Hone logo
Hone
Problems

Dynamic Form Fields with Vue and TypeScript

Building dynamic forms where the number of input fields can change at runtime is a common requirement in web development. This challenge focuses on implementing "field arrays" in Vue.js using TypeScript, allowing users to add, remove, and manage a list of similar input elements within a form.

Problem Description

You are tasked with creating a reusable Vue component that manages a dynamic list of form fields. This component, let's call it FieldArray, should allow users to add new instances of a specific field type to the array and remove existing ones. The component should be flexible enough to handle various field types (e.g., text inputs, numbers) and should maintain the state of these fields correctly.

Key Requirements:

  1. Dynamic Addition: The user should be able to click a button to add a new field to the array.
  2. Dynamic Removal: Each field instance should have a way to be removed from the array.
  3. State Management: The component must correctly manage the data for each field in the array.
  4. Reusability: The FieldArray component should be generic and configurable to accept different field types and their associated data structures.
  5. Vue 3 Composition API: Implement the solution using Vue 3's Composition API and TypeScript.

Expected Behavior:

When the component is rendered, it should display an initial set of fields (if any) and an "Add Field" button. Clicking "Add Field" should append a new, empty field to the list, along with its corresponding "Remove" button. Clicking a "Remove" button next to a field should delete that specific field from the list. All changes to the field array should be reflected in the component's data.

Edge Cases to Consider:

  • What happens when the array is empty?
  • What happens when the last field is removed?
  • How to handle initial data provided to the FieldArray?

Examples

Example 1: Adding and Removing Text Fields

Input (Initial Data):

{
  "items": [
    {"id": 1, "value": "Initial Item 1"},
    {"id": 2, "value": "Initial Item 2"}
  ]
}

Component Usage (Conceptual):

<template>
  <FieldArray
    :modelValue="formData.items"
    @update:modelValue="formData.items = $event"
    fieldKey="value"
    label="Item"
  >
    <template #field="{ item, index, updateItem }">
      <input
        type="text"
        :value="item.value"
        @input="updateItem(index, 'value', ($event.target as HTMLInputElement).value)"
      />
    </template>
  </FieldArray>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import FieldArray from './components/FieldArray.vue'; // Assuming FieldArray is in components/

const formData = ref({
  items: [
    { id: 1, value: "Initial Item 1" },
    { id: 2, value: "Initial Item 2" }
  ]
});
</script>

Expected Output (After User Actions):

  1. Initial Render: Displays two text fields with "Initial Item 1" and "Initial Item 2". Each has a "Remove" button. An "Add Item" button is present.
  2. User clicks "Add Item": A third text field appears with an empty value, and its own "Remove" button.
  3. User types "New Value" into the third field: The third field's value updates.
  4. User clicks "Remove" next to the first field: The first text field disappears. The remaining fields re-index or adjust their visual order.
  5. User clicks "Remove" next to the third (now second) field: The third field disappears.

Explanation: The FieldArray component takes an array of objects, a fieldKey to specify which property of the object to display/edit, and a label. A slot is used to define how each individual field should be rendered. The component internally manages adding/removing these objects and emits updates to the parent.

Example 2: Handling Different Field Types (Conceptual)

Imagine a scenario where the FieldArray could manage an array of contact information, where each contact might have a name (text) and age (number). The slot mechanism would allow rendering different input types within the FieldArray.

Input (Initial Data):

{
  "contacts": [
    { "id": 1, "name": "Alice", "age": 30 },
    { "id": 2, "name": "Bob", "age": 25 }
  ]
}

Component Usage (Conceptual):

<template>
  <FieldArray
    :modelValue="formData.contacts"
    @update:modelValue="formData.contacts = $event"
    fieldKey="name" // Example of a primary field key for display/removal context
    label="Contact"
  >
    <template #field="{ item, index, updateItem }">
      <div>
        <input
          type="text"
          :value="item.name"
          @input="updateItem(index, 'name', ($event.target as HTMLInputElement).value)"
          placeholder="Name"
        />
        <input
          type="number"
          :value="item.age"
          @input="updateItem(index, 'age', parseInt(($event.target as HTMLInputElement).value, 10))"
          placeholder="Age"
        />
      </div>
    </template>
  </FieldArray>
</template>

<script setup lang="ts">
import { ref } from 'vue';
import FieldArray from './components/FieldArray.vue';

const formData = ref({
  contacts: [
    { id: 1, name: "Alice", age: 30 },
    { id: 2, name: "Bob", age: 25 }
  ]
});
</script>

Expected Output: The FieldArray would manage the contacts array, allowing users to add/remove contacts. Within the slot, both name and age inputs would be managed for each contact object.

Constraints

  • Vue Version: Vue 3.x with the Composition API.
  • Language: TypeScript.
  • Data Structure: The array managed by FieldArray will consist of objects. Each object is expected to have a unique id property (string or number) to help with tracking.
  • No External Libraries (beyond Vue itself): Do not use third-party form libraries.
  • Performance: The component should handle a reasonable number of fields (e.g., up to 50-100) without significant performance degradation.

Notes

  • Consider using a unique identifier (id) for each item in the array to facilitate efficient removal and state updates. You might need to generate these IDs internally if they are not provided initially.
  • The FieldArray component should emit an updated model value when changes occur, following Vue's v-model convention.
  • Think about how you will handle the internal state of the array and how it will be passed to and from the parent component.
  • The slot provides the item data for the current field, its index, and a helper function updateItem(index, key, value) to easily update a specific property of an item in the array.
Loading editor...
typescript