documentation: validationContext option breaks validation

Describe the bug When useForm is used with the validationResolver option, and the identity of the resolver function changes, validation will break. Fields are no longer validated. When I hit Submit button, the resolver receives empty data object. In case of onBlur mode, on loss of focus the resolver is not called at all. The same behavior is manifested when a fresh validationContext object is passed to useForm on every render.

To Reproduce Steps to reproduce the behavior:

  1. Go to https://codesandbox.io/s/react-hook-form-validationresolver-1bzwb
  2. Enter “x” into the Username text box.
  3. Click outside of the text box. A validation error appears correctly.
  4. Enter “john” into the Username text box.
  5. Click outside of the text box.
  6. The value is not revalidated. The ErrorMessage does not disappear even though the value is valid (problem 1).
  7. Hit the Submit button.
  8. Check the console. The onSubmit callback was not executed, even though the value is now valid - problem 2.

Codesandbox link https://codesandbox.io/s/react-hook-form-validationresolver-1bzwb

Expected behavior Validation keeps working. After every keypress, username should be immediately re-validated. The error message should disappear when username is at least 3 characters long. On loss of focus the resolver should be called again (in the onBlur mode). When I hit the Submit button, the onSubmit callback should receive a data object with username value from input field.

Screenshots validationResolverBug

Desktop (please complete the following information):

  • OS: Ubuntu 16
  • Browser: Chrome
  • Version 79

Additional context The on blur / on input listener is unregistered in useForm.ts line 1138:

  useEffect(
    () => () => {
      isUnMount.current = true;
      fieldsRef.current &&
        Object.values(
          fieldsRef.current,
        ).forEach((field: Field | undefined): void =>
          removeFieldEventListenerAndRef(field, true),
        );
    },
    [removeFieldEventListenerAndRef],
  );

The hook enters the “isUnMount” state (which prevents reRender) and stays there forever (which is probably undesired). Listeners are unregistered, but new listeners are not registered for input elements. (Note that the callback returned from this effect is executed when removeFieldEventListenerAndRef changed its identity, which occurs when a different validationResolver function is passed to useForm). Expected: listeners-good Actual (after second render): listeners-missing

About this issue

  • Original URL
  • State: closed
  • Created 4 years ago
  • Reactions: 2
  • Comments: 16 (9 by maintainers)

Commits related to this issue

Most upvoted comments

For the use case: please see my first comment. The resolver may need access to additional data (aside from what is passed to it as arguments). The documentation does not seem to mention this limitation (only one validationResolver per component), therefore I reported it. IMO, it is needlessly limiting and breaks user expectations silently. Thanks for your efforts though, great lib.

hey @rlaffers i have fixed the cache issue with validationContext, will be released in the next patch.

I will update the doc on this part:

validationResolver: will be cached inside react-hook-form, because you want to cache the validation rules instead change during render, same as validation schema.

validationContext: is not cached and you can change then as your component gets re-rendered.

let me know if above makes sense.

To solve my use case, I did roughly this:


export default function App() {
  // create the context object in first render of this coemponent
  // useMemo does not come with semantic guarantee, use lodash.memoize or similar
  // see https://reactjs.org/docs/hooks-reference.html#usememo
  const context = useMemo(() => ({}), [])

  const date = new Date();
  console.log("date in render", date);
  // mutate the context object
  context.date = date;

  const { register, handleSubmit, errors } = useForm({
    mode: "onBlur",
    validationResolver,
    validationContext: context
  });

I…e. mutation of the memoized context object created during the first render of the component. While this may not be the best practice (mutated objects!), it works for me (until perhaps change is made to the hook to prevent memoizing the passed validationContext).

The resolver may need access to additional data (aside from what is passed to it as arguments).

The reason why I introduced validationContext, the lib itself cache validationResolver. Will validationContext solve your problem? if so I will update the doc to reflect the caching mechanism on the website.