jaredpalmer / formik

Build forms in React, without the tears 😭
https://formik.org
Apache License 2.0
33.87k stars 2.79k forks source link

Use mutation for top-level formik values object #1954

Open andyrichardson opened 4 years ago

andyrichardson commented 4 years ago

🚀 Feature request

Field-level validation is incredibly powerful but comes with a pretty notable limitation.

When calling 'validate' provided via useField, there is no way for the provided validation function to have access to the latest Formik state. It's just not currently possible.

Example

const { values } = useFormikContext();

const fieldArgs = useMemo(() => ({
  name: 'deliveryDate',
  validate: (fieldValue) => {
    if (values.pickUp) {
      return null;
    }

    return fieldValue ? null : 'This field is required'
  }
}), [values.pickUp]);
const [deliveryDateProps, deliveryDateMeta] = useField(fieldArgs)

In the above example, the validation called on useField will always be 1 render behind.

  1. deliveryDate function is passed to Formik
  2. pickUp value is changed to true
  3. deliveryDate validation fn from step 1 uses old values object in closure (returns valid)
  4. component is re-rendered with new values from useFormikContext
  5. deliveryDate validation call uses new values object in closure (returns invalid)

Suggested Solution

We remove step 4 + 5 from above and instead use a reference object for values returned from useFormikContext (values is updated with new values using mutation). This way we can always access the new values despite validation functions using the useFormikContext from a previous render.

In order to correctly set dependencies for memo hooks, actual values should be set as dependencies - not the top-level values object.

// Before
const hasDate = useMemo(() => values.date !== undefined, [values]);

// After
const hasDate = useMemo(() => values.date !== undefined, [values.date]);

Why would we want this?

jaredpalmer commented 4 years ago

I think this makes all the sense in the world. Can you make a PR?