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:
DynamicFormComponent: This component will be the main entry point. It should accept a propfieldsConfigwhich is an array of field configuration objects.- Field Configuration Object: Each object in
fieldsConfigshould 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 withvalueandlabelproperties, used for "select" input types.
- Dynamic Rendering: The
DynamicFormcomponent must render the correct HTML input element based on thetypespecified 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
onChangehandlers 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:
- When
DynamicFormmounts, it should initialize its internal state with theinitialValueof each field fromfieldsConfig. - For each
fieldConfiginfieldsConfig:- If
typeis "text", "number", or "email", render an<input type={type} />. - If
typeis "textarea", render a<textarea />. - If
typeis "select", render a<select />with<option>elements generated from theoptionsprop. - Each rendered field should have a corresponding
<label>associated with it using theidandhtmlForattributes.
- If
- When a user interacts with an input field (e.g., types, selects an option), the
DynamicForm's state should update. - 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
optionsfor "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
fieldsConfigprop will be an array of objects. - Each
idwithinfieldsConfigwill be unique. - The
typeproperty will be one of: "text", "number", "email", "textarea", "select". initialValuewill be of a type appropriate for thetype(string for text/email/textarea/select, number for number).- The number of fields in
fieldsConfigwill 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
fieldConfigobjects 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
onSubmitcallback). YourDynamicFormcomponent should just provide the current values. - For the "select" input, ensure you iterate over the
optionsto create the<option>elements. - Accessibility is important: ensure labels are correctly associated with their input fields.