Hone logo
Hone
Problems

Dynamic Form Field Generator in React

This challenge requires you to build a React component that can dynamically generate form fields based on a provided configuration. This is a common requirement in applications where users might need to input varying amounts of data, or where form structures need to adapt based on certain conditions. You will learn how to manage dynamic state and render components conditionally.

Problem Description

Your task is to create a reusable React component, DynamicForm, that accepts an array of field configurations and renders the corresponding form elements. Each field configuration object will specify the type of input, its label, a unique key, and any initial value. The component should handle user input, updating the form's state accordingly.

Key Requirements:

  • DynamicForm Component: This component will be the main entry point. It should accept a prop fieldsConfig which is an array of field configuration objects.
  • Field Configuration Object: Each object in fieldsConfig should have at least the following properties:
    • id: A unique string identifier for the field.
    • label: A string to be displayed as the label for the input field.
    • type: A string representing the input type (e.g., "text", "number", "email", "textarea", "select").
    • initialValue: The initial value for the input field.
    • options (optional): An array of objects with value and label properties, used for "select" input types.
  • Dynamic Rendering: The DynamicForm component must render the correct HTML input element based on the type specified in the configuration.
  • State Management: The component should maintain an internal state to hold the values of all form fields. When a user types into an input, the corresponding state should be updated.
  • Event Handling: Implement onChange handlers for all input elements to update the form's state.
  • Output: The component should also expose a way to access the current state of all form values. For this challenge, the parent component will render a button that, when clicked, logs the current form values to the console.

Expected Behavior:

  1. When DynamicForm mounts, it should initialize its internal state with the initialValue of each field from fieldsConfig.
  2. For each fieldConfig in fieldsConfig:
    • If type is "text", "number", or "email", render an <input type={type} />.
    • If type is "textarea", render a <textarea />.
    • If type is "select", render a <select /> with <option> elements generated from the options prop.
    • Each rendered field should have a corresponding <label> associated with it using the id and htmlFor attributes.
  3. When a user interacts with an input field (e.g., types, selects an option), the DynamicForm's state should update.
  4. A parent component will provide a button. Clicking this button should trigger a callback function passed from the parent, which receives the current form values object.

Edge Cases:

  • Empty fieldsConfig: The form should render nothing or an appropriate message.
  • Missing options for "select" type: The "select" input should render, but without any options (or handle as an error if preferred, though empty is acceptable for this challenge).
  • Unknown type: Any input types not explicitly handled should be ignored or rendered as a simple text input with a warning.

Examples

Example 1: Basic Text and Number Inputs

Input (fieldsConfig prop):
[
  { id: 'username', label: 'Username', type: 'text', initialValue: '' },
  { id: 'age', label: 'Age', type: 'number', initialValue: 0 }
]

Parent Component Usage:
<DynamicForm fieldsConfig={config} onSubmit={(values) => console.log(values)} />
<button onClick={() => handleSubmit()}>Log Values</button> // Assuming handleSubmit calls onSubmit

Expected Behavior & Output (after typing 'Alice' and '30'):
- Renders a "Username" label with a text input.
- Renders an "Age" label with a number input.
- After clicking "Log Values":
  Console Output: { username: 'Alice', age: 30 }

Example 2: Select and Textarea Inputs

Input (fieldsConfig prop):
[
  { id: 'country', label: 'Country', type: 'select', initialValue: 'USA', options: [{ value: 'USA', label: 'United States' }, { value: 'CAN', label: 'Canada' }] },
  { id: 'message', label: 'Your Message', type: 'textarea', initialValue: 'Hello!' }
]

Parent Component Usage:
<DynamicForm fieldsConfig={config} onSubmit={(values) => console.log(values)} />
<button onClick={() => handleSubmit()}>Log Values</button>

Expected Behavior & Output (after changing country to 'Canada' and editing message):
- Renders a "Country" label with a select dropdown.
- Renders a "Your Message" label with a textarea.
- After clicking "Log Values":
  Console Output: { country: 'CAN', message: 'Updated message content.' }

Example 3: Mixed Field Types and Initial Values

Input (fieldsConfig prop):
[
  { id: 'firstName', label: 'First Name', type: 'text', initialValue: 'John' },
  { id: 'lastName', label: 'Last Name', type: 'text', initialValue: 'Doe' },
  { id: 'email', label: 'Email Address', type: 'email', initialValue: 'john.doe@example.com' },
  { id: 'feedback', label: 'Feedback', type: 'textarea', initialValue: '' },
  {
    id: 'gender',
    label: 'Gender',
    type: 'select',
    initialValue: 'male',
    options: [
      { value: 'male', label: 'Male' },
      { value: 'female', label: 'Female' },
      { value: 'other', label: 'Other' }
    ]
  }
]

Parent Component Usage:
<DynamicForm fieldsConfig={config} onSubmit={(values) => console.log(values)} />
<button onClick={() => handleSubmit()}>Log Values</button>

Expected Behavior & Output (after changing email and feedback):
- Renders all specified fields with their initial values pre-filled.
- After clicking "Log Values":
  Console Output: {
    firstName: 'John',
    lastName: 'Doe',
    email: 'updated.email@example.com',
    feedback: 'Some feedback text.',
    gender: 'male'
  }

Constraints

  • The fieldsConfig prop will be an array of objects.
  • Each id within fieldsConfig will be unique.
  • The type property will be one of: "text", "number", "email", "textarea", "select".
  • initialValue will be of a type appropriate for the type (string for text/email/textarea/select, number for number).
  • The number of fields in fieldsConfig will not exceed 20.
  • The total number of options for any "select" field will not exceed 15.
  • The solution should be implemented in TypeScript.

Notes

  • Consider how to map the fieldConfig objects to state and to the rendered DOM elements.
  • Think about a robust way to handle the generic nature of the input values in your state.
  • The parent component will handle the submission logic (e.g., the button click and the onSubmit callback). Your DynamicForm component should just provide the current values.
  • For the "select" input, ensure you iterate over the options to create the <option> elements.
  • Accessibility is important: ensure labels are correctly associated with their input fields.
Loading editor...
typescript