I am building a form using an array
of objects
that describe the elements within the form.
const fields = [
{ name: "Name", type: "text", placeholder: "What's your fullname?" },
{ name: "Email", type: "email", placeholder: "We promise not to spam you." },
{ name: "Password", type: "password", placeholder: "Make it a good one!" },
{ name: "About", type: "textarea", placeholder: "Tell us a little about yourself ..." },
];
I map over each object to produce my form which all works as desired.
{fields.map((field, index) => (
<div key={form-field-${index}`}>
<label htmlFor={field.name}>{field.name}</label>
<div>
{field.type !== "textarea" &&
<input
type={field.type}
id={field.name}
name={field.name}
placeholder={field.placeholder}
/>
}
{field.type === "textarea" &&
<textarea
id={field.name}
name={field.name}
placeholder={field.placeholder}
/>
}
</div>
</div>
))}
As you can see I have some conditional rendering based on the type
value of each field
. For two different form elements this is not horrendous, but if I go adding other form elements (<select>
for example), I would prefer to not have x
conditionals if there is a better alternative.
Is there a better alternative? Move this to its a function, or its own component perhaps?
I am kind of hoping there is a means of doing something like:
{fields.map((field, index) => {
<field.formType
id="{field.name}"
...
/>
});
Where field.formType
maps to a formType: "input"
in the fields
array.
CodePudding user response:
You can actually do this if you assign it to a capitalised variable then use that (is a weird JSX gotcha). See https://reactjs.org/docs/jsx-in-depth.html#user-defined-components-must-be-capitalized.
{fields.map((field, index) => {
const Tag = field.formType
return <Tag
id={field.name}
...
/>
});
CodePudding user response:
I would say create separate components for each type of input (input, textarea, select, etc.) and keep using conditionals:
{fields.map((field, index) => (
<div key={form-field-${index}`}>
<label htmlFor={field.name}>{field.name}</label>
<div>
{field.type === "text" && <Input field={field} />}
{field.type === "email" && <Input field={field} />}
{field.type === "password" && <Input field={field} />}
{field.type === "textarea" && <Textarea field={field} />}
{field.type === "select" && <Select field={field} />}
</div>
</div>
))}
or if you can change the structure of the data in the array, add a property like formType
and use that to define which component to use:
{fields.map((field, index) => (
<div key={form-field-${index}`}>
<label htmlFor={field.name}>{field.name}</label>
<div>
{field.formType === "input" && <Input field={field} />}
{field.formType === "textarea" && <Textarea field={field} />}
{field.formType === "select" && <Select field={field} />}
</div>
</div>
))}
It's clean and you can use individual logic/state/etc. inside each component if needed
CodePudding user response:
You can provide a new field (property) like Component to your array of fields and there you can have anything you like, like Component:input, Component:select, Component: CustomComponent and then when mapping you render that component and provide other fields as props like: (inside map here)
const {Component, ...props} = field;
return <Component {...props}/>
note that for select you might need to create a custom component that when receives options as props knows how to handle them since select takes options as children and not as attributes (props)