Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to handle array of fields #11

Closed
json2d opened this issue Jul 1, 2017 · 16 comments
Closed

How to handle array of fields #11

json2d opened this issue Jul 1, 2017 · 16 comments

Comments

@json2d
Copy link

json2d commented Jul 1, 2017

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?

@jaredpalmer
Copy link
Owner

jaredpalmer commented Jul 1, 2017

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.

@jaredpalmer
Copy link
Owner

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

@eonwhite
Copy link
Collaborator

eonwhite commented Jul 2, 2017

@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} />

@jaredpalmer
Copy link
Owner

@bitstrider agree with @eonwhite 100%.

@json2d
Copy link
Author

json2d commented Jul 3, 2017

@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.

@jaredpalmer
Copy link
Owner

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

@bzuker
Copy link

bzuker commented Jul 7, 2017

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.

@dannief
Copy link

dannief commented Aug 13, 2017

@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.

@davidhernon
Copy link

@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.

@dannief
Copy link

dannief commented Aug 22, 2017

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

This was referenced Sep 5, 2017
@Kaijun
Copy link

Kaijun commented Nov 3, 2017

@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

@adamduncan
Copy link

adamduncan commented Jan 25, 2018

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?

@etaheri
Copy link

etaheri commented May 25, 2018

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

@Ranthalion
Copy link

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

@ronyfhebrian
Copy link

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

@JosefHobler
Copy link

JosefHobler commented Nov 20, 2022

I used useFormik and made it work like this

schema:
[ { value: 48 }, { value: 48 } ]

`
<input
type='number'
placeholder={intl.formatMessage({ id: "APP.MODAL.FEEDBACK.INPUT.NAME" })}
autoComplete='off'
{...formik.getFieldProps(index + ".value")}
className={clsx(
'form-control bg-transparent',
{
'is-invalid': formik.touched[index]?.value && formik.errors[index]?.value,
},
{
'is-valid': formik.touched[index]?.value && !formik.errors[index]?.value,
}
)}
/>
{formik.touched[index]?.value && formik.errors[index]?.value && (



{formik.errors[index]?.value}


)}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests