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

Way to dynamically modify validationSchema from component child of <Formik>? #1436

Closed
MikeSuiter opened this issue Apr 12, 2019 · 6 comments
Closed

Comments

@MikeSuiter
Copy link

In our app we allow hooks so customer can customize it and all their data has to go into the userData field. In our core app we just define it as an object and don't validate the contents, but since all their form data goes in there, need a way to update it for validation.

Here is a simplified schema

const schema = Yup.object().shape({
  general: Yup.object({
    id: Yup.string().default(''),
    name: Yup.string()
      .required()
      .max(64)
      .default(''),
    alias: Yup.string()
      .required()
      .max(64)
      .default(''),
    desc: Yup.string().default(''),
    comment: Yup.string().default(''),
    tags: Yup.array().default([]),
    schemaVersion: Yup.number().default(''),
    version: Yup.number().default(''),
    lastUpdateBy: Yup.string().default(''),
    lastUpdate: Yup.string().default(''),
    tenantId: Yup.string().default('')
  }),
  userData: Yup.object().default({})
});

The customer will write their own components like CustomerFields below.

<Formik validationSchema={() => schema} initialValues={record}>
  {formikProps => (
    <div>
      <GeneralFields />
      <CustomerFields />
    </div>
  )}
</Formik>;

In CustomerFields they can use the connect() HoC to get formik props but formik.validationSchema doesn't look like something I can get the current schema and add to it. Any ideas on how I can dynamically modify the validationSchema in CustomerFields?

@jaredpalmer
Copy link
Owner

Validation schema is just another prop. You can lift up some other state above your Formik component and then update it imperatively from a child component. Then you can alter the prop or return value of the function at will.

@spacesuitdiver
Copy link

spacesuitdiver commented Aug 26, 2020

Would be nice if validationSchema was a function that is curried with the FormikBag so we don't have to litter the component with an additional piece of state.

EDIT:
That said it seems it might? Anyone stumbling upon this, check this out #1228

@HiranmayaGundu
Copy link

Hey @jaredpalmer! I'm trying out the approach you suggested by lifting state up. What I'm trying is something along the lines of this

const DynamicForm = () => {
  const [ schema, updateSchema ] = React.useState(intialSchema);
  <Formik
      validationSchema={schema}
   >
     <Form>
        <GeneralComponents /> 
        <CustomComponents updateSchema={updateSchema} />
    </Form>
 </Formik>
}  

I don't want the future authors of accidentally wipe out the schema, so I created this hook

const useSchema = (defaultSchema) => {
  const [ schema, setSchema ] = React.useState(defaultSchema);

  const updateSchema = React.useCallback(
    (addedSchema) => {
      const newSchema = schema.concat(addedSchema);
      setSchema(newSchema);
    },
    [ schema, setSchema ]
  );
  return [ schema, updateSchema ];
};

And in <CustomComponents /> do something like:

const CustomComponent = () => {
 React.useEffect(() => {
      const expressionSchema = Yup.object({
        description: Yup.string(),
        value: Yup.string().required('Value cannot be empty!')
      });
    updateSchema(expressionSchema);
  }, [ updateSchema ]);
 return (
 // form components rendered here 
)
}

However this results in an infinite loop. I suspect this is because the schema reference is different each time, but I'm not sure how. For now I'm avoiding this by not passing updateSchema to the dependency array, but the linter is complaining.

My understanding of hooks is a bit shallow right now, but my understanding of this is since updateSchema is closing over schmea which is state, I need to pass updateSchema in the dependency array to useEffect.

So I'm not sure what exactly I need to do in this situation 😅 Is there anyway to fix this?

@blentz100
Copy link

blentz100 commented Oct 26, 2022

The tip from @jaredpalmer worked for me. Thank you.

For reference, here is how I did it:

function MyComponent() {

    //setup state variable for new Schema, basing it on the original schema
    let [newValidationSchema, setNewValidationSchema] = useState(validationSchema);
    
    ...
    
    //some condition happens in which I want to update the validation schema at runtime
    //step 1 - create the new schema
    newValidationSchema = validationSchema.concat( 
        Yup.object().shape({
            entity: Yup.object({
                name: Yup.string().notOneOf([someUserGeneratedVariableHere]),
            }),
        })
    );
    //step 2 - update it using the useState hook
    setNewValidationSchema(newValidationSchema)

    return (
        <Formik
            initialValues={initialValues}
            onSubmit={handleSubmit}
            enableReinitialize={true}
            validationSchema={newValidationSchema}
        >
            ......
        </Formik>
    )
}

@hkiame
Copy link

hkiame commented Dec 30, 2022

The tip from @jaredpalmer worked for me. Thank you.

For reference, here is how I did it:

function MyComponent() {

    //setup state variable for new Schema, basing it on the original schema
    let [newValidationSchema, setNewValidationSchema] = useState(validationSchema);
    
    ...
    
    //some condition happens in which I want to update the validation schema at runtime
    //step 1 - create the new schema
    newValidationSchema = validationSchema.concat( 
        Yup.object().shape({
            entity: Yup.object({
                name: Yup.string().notOneOf([someUserGeneratedVariableHere]),
            }),
        })
    );
    //step 2 - update it using the useState hook
    setNewValidationSchema(newValidationSchema)

    return (
        <Formik
            initialValues={initialValues}
            onSubmit={handleSubmit}
            enableReinitialize={true}
            validationSchema={newValidationSchema}
        >
            ......
        </Formik>
    )
}

Thanks for this. Took a long time to find the solution. Hope they add to the documentation.

@matroskin92
Copy link

It's work! I wrote examples
https://codesandbox.io/s/quirky-pascal-rnmhpc?file=/src/App.js

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

No branches or pull requests

7 participants