How to use react-hook-form dynamic resolver validation?

2.2k Views Asked by At

I'm using react-hook-form for form submitting, and I need to make the validation work dynamically. For example, in the form, the category has three options: 'firstCateogory', 'secondCategory', 'thirdCategory'.

If the user chooses the category "firstCategory", the schema should be dynamically changed: the version input should be changed from 'Yup.string().nullable()' to 'Yup.string().nullable().required('Version is required')', required while the other two category options don't have the version required validation.

 const Schema = Yup.object().shape({
        category: Yup.string().nullable().required('Category is required'),
        title: Yup.string().nullable().required('Title is required'),
        version: Yup.string().nullable()
    });

 const DynamicSchema = Yup.object().shape({
        category: Yup.string().nullable().required('Category is required'),
        title: Yup.string().nullable().required('Title is required'),
        version: Yup.string().nullable().required('Version is required')
    });


 const methods = useForm({
     resolver: yupResolver(Schema),
     defaultValues,
    });

const {
        getValues,
        setValue,
        watch,
        handleSubmit,
        formState: { isSubmitting },
    } = methods;

...

return (
    <FormProvider methods={methods} onSubmit={handleSubmit(onSubmit)}>
       <RHFSelect
            name="category"
            label="Category">
           {CATEGORY.map((item) => (
                        <MenuItem >
                            {item.label}
                        </MenuItem>
                    ))}
                </RHFSelect>

          {watch('category') === 'firstCategory' && (
                    <RHFTextField
                        name="version"
                        label="Version"
                    />
                )}


I've read a lot of things about this issue, but nothing really helped. In this following code, I've tried to make the resolver dynamically change, but I couldn't use watch method here, it is not defined yet.

     resolver: yupResolver(Schema), // resolver: watch('category') === 'firstCategory' ? yupResolver(Schema) : yupResolver(DynamicSchema)
     defaultValues,
    });

Is there a way to use the resolver dynamically change?


This is the code that I've changed based on your answer.

import {Controller, useForm} from 'react-hook-form';

const Schema = Yup.object().shape({
        category: Yup.string().nullable().required('Category is required'),
        title: Yup.string().nullable().required('Title is required'),
        version: Yup.string().when('category', (value) => {
            if (value[0] === 'category1') {
                return Yup.string().required('Version is required')
            } else if (value[0] === 'category2' || value[0] === 'category3') {
                return Yup.string().nullable()
            }
        }),
    });

 const {
            getValues,
            setValue,
            watch,
            handleSubmit,
            control,
            formState: { isSubmitting, errors },
        } = useForm({
            resolver: yupResolver(Schema),
            defaultValues,
        });

...

return (
 <FormProvider onSubmit={handleSubmit(onSubmit)}>
       <FormControl>
             <Controller
                 name="category"
                 control={control}
                 render={({field}) => (
                     <RHFSelect
                         {...field}
                         label={<EssentialLabel text="Category" />}
                      >
                           {CATEGORY_LIST.map((item) => (
                                <MenuItem key={item.code}
                                          value={item.code}
                                          onClick={() =>
                                             field.onChange(item)
                                          }>
                                        {item.label}
                                </MenuItem>
                             ))}
                         </RHFSelect>
                    )}
                 />
        </FormControl>
</FormProvider>
)
1

There are 1 best solutions below

4
Kasetti Jhansi Sai Anusha On

To achieve dynamic validation there is no need for two separate schemas, you can implement a single schema and dynamically apply validation rules based on the field value and using when. Below I am giving my code of how I performed dynamic validation

import React from 'react';
import * as yup from 'yup';
import { FormControl, Menu, MenuButton, MenuItem, MenuList, FormLabel, Input, Button, Box } from '@chakra-ui/react';
import { useForm, Controller } from 'react-hook-form';
import { yupResolver } from '@hookform/resolvers/yup';

const validationSchema = yup.object().shape({
  category: yup.string().nullable().required('Category is required'),
  title: yup.string().nullable().required('Title is required'),
  version: yup.string().when('category', (value)=>{
    if(value[0] === "category2" || value[0] === "category3"){
        return yup.string().nullable()
    }else{
        return yup.string().required('Version is required')
    }
  })
});

function YupValidation() {
  const categoryList = ["category1", "category2", "category3"];
  const {
    handleSubmit,
    control,
    formState: { errors }
  } = useForm({
    defaultValues: {
      category: "",
      title: "",
      version: ""
    },
    resolver: yupResolver(validationSchema),
  });

  const onSubmit = async(data) => {
    console.log("successfully submitted",data);
  }

  return (
    <form onSubmit={handleSubmit(onSubmit)}>
      <FormControl>
        <FormLabel mt="1rem">
          Category <span style={{ color: "red" }}>*</span>
        </FormLabel>
        <Controller
          name="category"
          control={control}
          render={({ field }) => (
            <Menu>
              <MenuButton as={Button}>
                {field.value ? field.value : "Select Category"}
              </MenuButton>
              <MenuList>
                {categoryList.map((item, index) => (
                  <MenuItem key={index} onClick={() => field.onChange(item)}>{item}</MenuItem>
                ))}
              </MenuList>
            </Menu>
          )}
        />
        {errors.category && (
          <p style={{ color: "red" }}>{errors.category.message}</p>
        )}
      </FormControl>
      <FormControl>
        <FormLabel mt="1rem">
          Title <span style={{ color: "red" }}>*</span>
        </FormLabel>
        <Controller
          name="title"
          control={control}
          render={({ field }) => (
            <Input {...field} />
          )}
        />
        {errors.title && (
          <p style={{ color: "red" }}>{errors.title.message}</p>
        )}
      </FormControl>
      <FormControl>
        <FormLabel mt="1rem">
          Version 
        </FormLabel>
        <Controller
          name="version"
          control={control}
          render={({ field }) => (
            <Input {...field} />
          )}
        />
        {errors.version && (
          <p style={{ color: "red" }}>{errors.version.message}</p>
        )}
      </FormControl>
      <Box display="flex" justifyContent="center">
        <Button
          type="submit"
          width="20rem"
          color="white"
          mb="2rem"
          mt="2rem"
          bgColor="blue"
        >
          Submit
        </Button>
      </Box>
    </form>
  )
}

export default YupValidation;