Formik bind array objects dynamically

335 Views Asked by At

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.

1

There are 1 best solutions below

0
Ahmet Firat Keler On BEST ANSWER

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)