I'm new to Formik and need to create dynamic form which can contain multiple entries of the same type. I use a list of objects to keep track of newly added entries as follows.
-- At the rendering element level
const [vehicles, setVehicles] = useState([{make: "", model: "", year: ""}]);
In the render function, within the form I use map to render multiple input elements, bind with above objects. I can't use Formik initialValues
since there will be new entries added by the user upon a button click.
{vehicles.map((vehicle, i) => {
return (
<div key={i}>
<TextField
fullWidth
label="Make"
name="make"
onBlur={handleBlur}
onChange={handleChange}
value={vehicle.make}
variant="outlined"
/>
<!-- other input boxes as well -->
</div>
);
})};
The problem here is, I can't update value of text box since vehicle.maker
isn't updating when I type in the text box.
If you are going to use array of objects, I suggest you to implement it this way. You can still use initialValues even if there are going to be new entries.
First, generate your validation schema
import * as Yup from 'yup';
const vehiclesValidation = Yup.object().shape({
vehicles: Yup.array()
.of(
Yup.object().shape({
make: Yup.string().required('This is required'),
model: Yup.string().required('This is required'),
year: Yup.string().required('This is required'),
})
)
.unique('Model must be unique', (val: any) => val.model)
.required('Must have vehicles')
.min(1, 'Minimum of 1 vehicle'),
});
Write your initial values
const vehicleInitialValues = {
make: '',
model: '',
year: '',
};
Import
import
{
FormFeedback,
Input,
Label,
} from 'reactstrap';
import { FieldArray, Formik } from 'formik';
Implement
<Formik
validationSchema={vehiclesValidation}
initialValues={{ vehicles: [vehicleInitialValues] }}
onSubmit={async (values) => {}}
>
{({
values,
touched,
errors,
handleChange,
handleBlur,
handleSubmit,
setFieldValue,
setFieldTouched,
}) => (
<Form
onSubmit={async (e) => handleSubmit(e)}
>
<FieldArray
name="vehicles"
render={(arrayHelpers) => {
return (
<div className="row m-0">
{values.vehicles.map((vehicle, index) => {
return (
<div className="row m-0">
<div className="col-lg-6 px-1 py-2">
<Label className="form-label">
Make
<span className="text-danger">
*
</span>
</Label>
<Input
type="text"
label="MAKE"
maxLength={50}
className="form-control"
placeholder="Make"
value={vehicle.make}
name={`vehicles.${index}.make`}
invalid={
!!(
touched &&
touched.vehicles &&
touched.vehicles.length > 0 &&
touched.vehicles[index] &&
touched.vehicles[index]['make'] &&
errors &&
errors.vehicles &&
errors.vehicles.length > 0 &&
errors.vehicles[index] &&
errors.vehicles[index]['make']
)
}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched &&
touched.vehicles &&
touched.vehicles.length > 0 &&
touched.vehicles[index] &&
touched.vehicles[index]['make'] &&
errors &&
errors.vehicles &&
errors.vehicles.length > 0 &&
errors.vehicles[index] &&
errors.vehicles[index]['make'] ? (
<FormFeedback type="invalid">
{
errors.vehicles[index]['make']
}
</FormFeedback>
) : null}
</div>
<div className="col-lg-6 px-1 py-2">
<Label className="form-label">
Model
<span className="text-danger">
*
</span>
</Label>
<Input
type="text"
label="MODEL"
maxLength={50}
className="form-control"
placeholder="Model"
value={vehicle.model}
name={`vehicles.${index}.model`}
invalid={
!!(
touched &&
touched.vehicles &&
touched.vehicles.length > 0 &&
touched.vehicles[index] &&
touched.vehicles[index]['model'] &&
errors &&
errors.vehicles &&
errors.vehicles.length > 0 &&
errors.vehicles[index] &&
errors.vehicles[index]['model']
)
}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched &&
touched.vehicles &&
touched.vehicles.length > 0 &&
touched.vehicles[index] &&
touched.vehicles[index]['model'] &&
errors &&
errors.vehicles &&
errors.vehicles.length > 0 &&
errors.vehicles[index] &&
errors.vehicles[index]['model'] ? (
<FormFeedback type="invalid">
{
errors.vehicles[index]['model']
}
</FormFeedback>
) : null}
</div>
<div className="col-lg-6 px-1 py-2">
<Label className="form-label">
Year
<span className="text-danger">
*
</span>
</Label>
<Input
type="text"
label="YEAR"
maxLength={50}
className="form-control"
placeholder="Year"
value={vehicle.year}
name={`vehicles.${index}.year`}
invalid={
!!(
touched &&
touched.vehicles &&
touched.vehicles.length > 0 &&
touched.vehicles[index] &&
touched.vehicles[index]['year'] &&
errors &&
errors.vehicles &&
errors.vehicles.length > 0 &&
errors.vehicles[index] &&
errors.vehicles[index]['year']
)
}
onChange={handleChange}
onBlur={handleBlur}
/>
{touched &&
touched.vehicles &&
touched.vehicles.length > 0 &&
touched.vehicles[index] &&
touched.vehicles[index]['year'] &&
errors &&
errors.vehicles &&
errors.vehicles.length > 0 &&
errors.vehicles[index] &&
errors.vehicles[index]['year'] ? (
<FormFeedback type="invalid">
{
errors.vehicles[index]['year']
}
</FormFeedback>
) : null}
</div>
</div>
)
})}
</div>
<div className="my-3">
<button
type="button"
className="w-100 btn font-size-14 btn-block"
onClick={() => {
arrayHelpers.push(vehicleInitialValues); // we push a new object
}}
>
Add New
</button>
)
}}
/>
</Form>
</Formik>
NOTE: If you want to remove an item, you can use arrayHelpers.remove(index)
(index from values.vehicles.map function)