Formik: How to handle array of fields

Created on 1 Jul 2017  路  15Comments  路  Source: formium/formik

Hi, so say my user API had a nested array like this:

{
   id: string,
   email: string,
   pets: [
     { type: string, name: string }
   ]
}

Is there a way to implement this kind of form, where i'll be able to add 0-many pets?

Most helpful comment

@bitstrider @jaredpalmer

While you can take the above approach if you want, it's worth noting you're not required to flatten the values out. Formik will work fine with complex nested data structures within values -- you just have to handle them in your render and handlers. Flattening is useful when using regular HTML inputs, but you can always design your own components which deal with whatever data structures you want.

What I would probably do, if your API is taking an arbitrary number of pets, is build a controlled component called PetsInput that takes your nested array and an value prop, and an onChange callback that uses the handleChangeValue signature. You could design PetsInput as you wish, including add/delete/reorder controls if you wanted, all contained within that reusable component.

Then from your Formik form render you would just do something like:

<label>Pets</label>
<PetsInput id="pets" value={props.values.pets} onChange={props.handleChangeValue} />

All 15 comments

Yes, you can create a mapPropsToValues function that would flatten the array and prefix/suffix each item.

const withFormik = Formik(
  ...
  mapPropsToValues: ({id, email, pets}) => ({
      id,
      email,
      ...(pets.reduce((accum, current, index) => Object.assign(accum, {
        [`pet_type_{index}`]: current.type,
        [`pet_name_{index}`]: current.name,
      }), {})
    })
  ...
)

This results in props.values that's equivalent to:

{
   id: string,
   email: string,
   pet_type_1: string,
   pet_name_1: string,
   pet_type_2: string,
   pet_name_2: string,
   ...
   pet_type_n: string,
   pet_name_n: string,
}

There are many ways to then display the pets. Using Object.keys and filter what I usually use in this situation. However, it really depends on your requirements. In the case of arbitrarily adding pets, you'll need to use props.setValues which allows you to declaratively call setState on just the key of Formik.state.values. This will modify props.values. If you need to rearrange or delete pets arbitrarily, you would not use the pets array index, but rather a uuid to uniquely identify each pet in the flattened state tree. Anyways, here's the simplest case (when the number of pets doesn't change).

// example of a form with a nested array that doesn't change size
const myForm = ({  values, handleSubmit, handleChange }) => (
  <form onSubmit={handleSubmit}>
    <input type="email" onChange={handleChange} id="email" />
    {Object.key(values).filter(v => v.startsWith('pet')).map((pet, i) => {
      <span>
        <input type="text" id={`pet_type_${i}`} value={values[`pet_type_${i}`]} onChange={handleChange} />
        <input type="text" id={`pet_name_${i}`} value={values[`pet_name_${i}`]} onChange={handleChange} />
      </span>
    })}
    <button type="submit">Submit</button>
  </form>
)

Hope that helps.

Note you would use the same .reduce method trick to create a properly keyed validationSchema and mapValuesToPayload, respectively

@bitstrider @jaredpalmer

While you can take the above approach if you want, it's worth noting you're not required to flatten the values out. Formik will work fine with complex nested data structures within values -- you just have to handle them in your render and handlers. Flattening is useful when using regular HTML inputs, but you can always design your own components which deal with whatever data structures you want.

What I would probably do, if your API is taking an arbitrary number of pets, is build a controlled component called PetsInput that takes your nested array and an value prop, and an onChange callback that uses the handleChangeValue signature. You could design PetsInput as you wish, including add/delete/reorder controls if you wanted, all contained within that reusable component.

Then from your Formik form render you would just do something like:

<label>Pets</label>
<PetsInput id="pets" value={props.values.pets} onChange={props.handleChangeValue} />

@bitstrider agree with @eonwhite 100%.

@jaredpalmer @eonwhite thanks these solutions work, and perhaps should get included in the README since array of fields is a common use case for forms IMO (esp say on admin panels).

I think mapPropsToValue and mapValuesToPayload adds an interesting degree of freedom, but in general I would prob end up avoid having to manage multiple data shapes.

Formik will pass all props that are not function into values automatically. If you don鈥檛 specify mapValuesToPayload, all values become payload by default. These methods are meant to help organize your code. They make refactoring much easier

how would I use the handleChangeValue to update the pet[0].name value with the approach @eonwhite
suggested?

I can't seem to find a way to update a particular object in an array.

@jaredpalmer

Note you would use the same .reduce method trick to create a properly keyed validationSchema and mapValuesToPayload, respectively

Am I right in assuming that if I wanted to dynamically add inputs that should be validated, then the implementation that you described would not work, since the yup schema is set within the formik config and cannot be updated dynamically?

Are there any plans to better support this scenario? I am thinking that without requiring the developer to manually flatten the values and schema, if we provide a mapping such as pets[1].type , it could just work and easily support dynamic forms. I am currently using the form library by the author of yup called react-formal and this is the approach used. I like HOC and flexible approach of formik though and wanted to evaluate it for another project. It is not so flexible in this scenario so it's stopping me from going forward with it right now. But great job by the way.

@dannief You can use Yups lazy function in order to create a schema at validation time.

Using this allowed me to add validation schema for dynamically created inputs.

@davidhernon Thanks for the tip. Will check out the lazy function

@jaredpalmer I think there could be some kind of implementations like the <Filed>, we could add a new HoC <Fields /> to automate mapping array to fields item_1, item_2 ... and assembling them back to array while calling onSubmit

Hey @davidhernon - could you please elaborate on using Yup's lazy method in conjunction with Formik's validationSchema to produce validation rules for dynamically added fields?

For anyone ending up here. Formic has a <FieldArray /> component that solves this! FieldArray Docs 馃槉

For anyone ending up here. Formic has a <FieldArray /> component that solves this! FieldArray Docs 馃槉

Field Array docs moved to https://jaredpalmer.com/formik/docs/api/fieldarray

What about arrays with useFormik?
I have already used useFormik and now kinda confused of how to make arrays of components from initialValues, the values are updating but the component doesn't.
Please help

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Jucesr picture Jucesr  路  3Comments

pmonty picture pmonty  路  3Comments

jaredpalmer picture jaredpalmer  路  3Comments

dearcodes picture dearcodes  路  3Comments

giulioambrogi picture giulioambrogi  路  3Comments