Sapan Diwakar

Software developer

Follow me on Twitter Check out my code on GitHub View some of my designs on Dribbble Take a look at my Linked In profile

Runtime validation with react-hook-form

React Hook Form is quickly gaining popularity as the goto form library for React. And with good reason. The API is really well thought, the validation is amazingly easy and the re-renders very efficient. One thing that I often find missing though is that it doesn't allow setting up validations based on another field's value. For example, you could have different validation rules for a zipcode text field based on the country that the user has selected.

In this post, I am going to show you How to validate a field depending on the value of another field?

State Variable for validation

For simplicity for the sake of this post, let us just consider that we want to make a field required based on another field's value. Once that is done, it is easy to switch out that required validation for anything else without much trouble. You might be tempted to write a code like this:

const [required, setRequired] = useState(false);  
return (  
  <label>
    <input name="required" type="checkbox" onChange={(e) => setRequired(e.currentTarget.checked)} ref={register} />
  </label>
  <input name="foo" type="text" ref={register({ required })} />
);

On the first look, it looks ok, and seems that this would work. Go ahead, try it out. The reason it doesn't though is that the ref is only invoked the first time the input in mounted and after that, it keeps using that same value.

Unregister

This is where unregister comes into play. All you need to do is, instead of just setting that required value in the change event, unregister the second input and register it again with a different rule. Here's how this can be done:

const [required, setRequired] = useState(false);  
const previousRequired = usePrevious(required);  
const ref = useRef<HTMLInputElement>();

useEffect(() => {  
  if (previousRequired !== required) {
    unregister('foo');
    register(ref.current, { required } as ValidationRules);
  }
}, [ref, required, previousRequired, register, unregister]),

return (  
  <label>
    <input name="required" type="checkbox" onChange={(e) => setRequired(e.currentTarget.checked)} ref={register} />
  </label>
  <input name="foo" type="text" ref={ref} />
);

Now, if you had to do any complex validation change at runtime, all you need is to create the correct ValidationRules in the register and you should be good to go. For example, here's how you could add validation for the input to be exactly n characters.

{
  maxLength: {
    message: `must be ${n} characters`,
    value: n,
  },
  minLength: {
    message: `must be ${n} characters`,
    value: n,
  },
};