formik nested dynamic object

Viewed 706

Could you please advise a solution for the following:

e.g. we have a checkout form with many delivery options, each requires different details, such as:

  • in store - requires only storeId
  • delivery office - requires cityId and officeId
  • courier - requires address fields (street, building)

example of state:

...,
delivery: {
  type: 'inStore',
  details: { storeId: 1 }
}

or

...,
delivery: {
  type: 'courier',
  details: {
    address: {
      street: '12 Ave',
      ...,
     }
   }
 }

As soon as only one of the above options could be checked I tried to use a radio button component to define a delivery type Also, it is possible to render different components based on delivery type (e.g. via switch-case) and fill different delivery.___ fields.

But if we fill the form for the inStore option and then switch to e.g. courier option the storeId property still is in the state. So how could this switch be properly handled (to left only required properties based on the selected delivery type)

2 Answers

As for now, I've added subform state reset to the radio button onChange event handler. So based on a new radio button value (e.g. deliveryType) new initial state calculated (e.g. deliveryDetails).

Because of this, the new deliveryType and the new initialState allow rendering related sub-forms without any trouble.

please check the solution here: https://codesandbox.io/s/formik-example-forked-ix0nt?file=/deliveryDetailsForm.js

It is worth mentioning that it is needed to update the subform initial state first, and then updating the dependent variable (e.g. deliveryType). So as soon as values.delivery.details object is updated before the required sub-form rendered the 'controlled-to-uncontrolled' error won't happen.

P.S. current solution uses switch-case to decide which initialState and which sub-form should be rendered, which is quite a bad design, so if you have any suggestions on how to make it better I would be really glad to hear them.

In your case React try to re-use the same input component for both first Field of store and courier case so it doesn't have to render a complete new node. You can easily fix that by adding an unique key prop to each Field component:

  switch (deliveryType) {
    case "store":
      return (
        <Field
          key={`${name}.storeId`}
          name={`${name}.storeId`}
          value={1}
          placeholder="storeId"
        />
      );
    case "courier":
      return (
        <React.Fragment>
          <Field
            key={`${name}.address.street`}
            name={`${name}.address.street`}
            placeholder="street"
          />
          <Field
            key={`${name}.address.building`}
            name={`${name}.address.building`}
            placeholder="building"
          />
          <Field
            key={`${name}.address.flat`}
            name={`${name}.address.flat`}
            placeholder="flat"
          />
        </React.Fragment>
      );
    case "delivery_office":
      return (
        <React.Fragment>
          <Field
            key={`${name}.cityId`}
            name={`${name}.cityId`}
            placeholder="cityId"
          />
          <Field
            key={`${name}.officeId`}
            name={`${name}.officeId`}
            placeholder="officeId"
          />
        </React.Fragment>
      );
    default:
      return null;
  }

Edit Formik Example (forked)

Related