Search code examples
reactjsformikformik-material-ui

Formik bind array objects dynamically


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.


Solution

  • 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)