React-hook-form: Enable Proxy check with React Native

Created on 9 Apr 2020  路  34Comments  路  Source: react-hook-form/react-hook-form

I have register screen with 7 TextInput, when call register function the validationResolver its triggered before mounted components and produce two initial renders, i use yup validation and fixed it, verifying its Screen was mounted;

The bug its fixed add isMounted mutable ref, but because happen this? what its the best solution?


import * as React from 'react';

const useYupValidationResolver = (validationSchema: any) => {
  const isMounted = React.useRef<boolean | undefined>();
  React.useEffect(() => {
    isMounted.current = true;
  }, []);

  return React.useCallback(
    async (data, validationContext) => {
      if (!isMounted.current) //<-- this prevent re-render
        return {
          values: data,
          errors: {},
        };
      try {
        const values = await validationSchema.validate(data, {
          abortEarly: false,
        });
        return {
          values,
          errors: {},
        };
      } catch (errors) {
        return {
          values: {},
          errors: errors.inner.reduce(
            (allErrors: Array<any>, currentError: any) => ({
              ...allErrors,
              [currentError.path]: {
                type: currentError.type ?? 'validation',
                message: currentError.message,
              },
            }),
            {},
          ),
        };
      }
    },
    [validationSchema],
  );
};

export default useYupValidationResolver;

System:
OS: Windows 10 10.0.18363
CPU: (4) x64 Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
Memory: 9.44 GB / 19.88 GB
Binaries:
Node: 10.16.3 - C:\Program Files\nodejs\node.EXE
Yarn: 1.18.0 - C:\Program Files (x86)\Yarn\binyarn.CMD
npm: 6.9.0 - C:\Program Files\nodejs\npm.CMD
Watchman: Not Found
SDKs:
Android SDK:
API Levels: 23, 26, 27, 28, 29
Build Tools: 23.0.1, 27.0.3, 28.0.3, 29.0.2
System Images: android-28 | Intel x86 Atom_64, android-28 | Google APIs Intel x86 Atom, android-29 | Google APIs Intel x86 Atom
Android NDK: 20.1.5948944
IDEs:
Android Studio: Version 3.5.0.0 AI-191.8026.42.35.6010548
Languages:
Java: 1.8.0_221 - C:\Program Files\Java\jdk1.8.0_221\bin\javac.EXE
Python: 2.7.16 - C:\Python27\python.EXE
npmPackages:
@react-native-community/cli: Not Found
react: Not Found
react-native: 0.62.2 => 0.62.2

enhancement improve documentation

Most helpful comment

Thanks for raising the issue @punisher97 sorry for the doc is not reflect this as well. Going to update it now.

thanks for answering and your time

All 34 comments

take a look this example: https://codesandbox.io/s/react-hook-form-validationresolver-b3phr

reproduce in a CSB then we can take a look.

Please check this snack with iOS or Android, i dont know if register or validationResolver:

https://snack.expo.io/@msvargas97/react-native-hook-form

oh that only happens on expo snack, I saw this before. can you double-check on your device?

oh that only happens on expo snack, I saw this before. can you double-check on your device?

In my project, i use react-native-cli, and yes, i testing with iOS device 13.3.1

Screen Shot 2020-04-11 at 8 22 33 am

Take a look the screenshot, expo snack render twice at App level.

Screen Shot 2020-04-11 at 8 22 33 am

Take a look the screenshot, expo snack render twice at App level.

If that is what I mean, it is not because of expo, it is because of the validationResolver, if you disable it and register function in TextInput, it renders once, the same happens on the real device, sorry if I did not make myself understood, sorry for my English, if you look at the useYupValidationResolver, there are a few comments to prevent the app from rendering twice,
if you want please have a snack one more time

That's all good @punisher97 take a look this example: where i follow the standard approach:

https://snack.expo.io/@bluebill1049/react-native-hook-form

In the meantime, i will take a look at the codebase see if anything is suspicious as well.

That's all good @punisher97 take a look this example: where i follow the standard approach:

https://snack.expo.io/@bluebill1049/react-native-hook-form

Yes, your snack is correct, now imagine when you have to render a form many TextInputs, if there is anything that can help you, let me know, thanks for answering, I thought it was something that only happened to me :),

Yes, your snack is correct, now imagine when you have to render a form many TextInputs

what do you mean? the validation resolver is a callback gets run during validation and return values and errors. it shouldn't matter.

i am still looking at your snack and try to understand what's trigger that extra re-render.

by the way your resolver actually return an error
Screen Shot 2020-04-11 at 10 07 19 am

by the way your resolver actually return an error
Screen Shot 2020-04-11 at 10 07 19 am

Yes, but because validationResolver its triggering before mount? its normal?

that's not normal, it should only do that if you are reading formState isValid.

oh make sense now. yes in React Native, the function will be evaluated due to Proxy. Sorry take a while to remember that, so if you return an error right away, it will trigger re-render.

There is no Proxy in React Native unlike Web, so we will have to evaluate form validate or not during initial run.

I will update the doc about formState, in React Native you can't omit not reading formState such as isValid. That's why the initial validation kicks in.

oh make sense now. yes in React Native, the function will be evaluated due to Proxy. Sorry take a while to remember that, so if you return an error right away, it will trigger re-render.

There is no Proxy in React Native unlike Web, so we will have to evaluate form validate or not during initial run.

Oh ok!, my solution to check isMounted thats right for now, thanks

Thanks for raising the issue @punisher97 sorry for the doc is not reflect this as well. Going to update it now.

Thanks for raising the issue @punisher97 sorry for the doc is not reflect this as well. Going to update it now.

thanks for answering and your time

Updated on the doc under formState section:

This reduces re-render feature only applies to the Web platform due to a lack of support on Proxy at React Native.

@bluebill1049 Hi, i testing with this polyfill to use Proxy, es6-proxy-polyfill and working fine in react native, do you could add this polyfill?


--- a/node_modules/react-hook-form/dist/react-hook-form.js
+++ b/node_modules/react-hook-form/dist/react-hook-form.js
@@ -15,6 +15,8 @@ var isObject = (value) => !isNullOrUndefined(value) && !isArray(value) && isObje

 var isHTMLElement = (value) => isObject(value) && value.nodeType === Node.ELEMENT_NODE;

+ const Proxy = typeof navigator != UNDEFINED && navigator.product === 'ReactNative' 
+ ? require('es6-proxy-polyfill') : typeof window !== UNDEFINED 
+ ? window.Proxy : undefined;

@@ -696,7 +702,7 @@ function useForm({ mode = VALIDATION_MODE.onSubmit, reValidateMode = VALIDATION_
     const isWeb = typeof document !== UNDEFINED &&
         !isWindowUndefined &&
         !isUndefined(window.HTMLElement);
-    const isProxyEnabled = isWeb && 'Proxy' in window;
+    const isProxyEnabled = isWeb && Proxy;

hey @punisher97 hmmm maybe I should include that in the doc :) I didn't want to include the polyfill because it will have an impact on the lib's bundle size. If you have time, feel free to send a PR on the website to info RN users' can import Proxy polyfill to get better Perf.

hey @punisher97 hmmm maybe I should include that in the doc :) I didn't want to include the polyfill because it will have an impact on the lib's bundle size. If you have time, feel free to send a PR on the website to info RN users' can import Proxy polyfill to get better Perf.

Ah understand, but I think it is better if you create an option to enable the proxy and pass createFactoryProxy or something similar?

I think it's a lot easier just import the polyfill at app level.

I think it's a lot easier just import the polyfill at app level.

If you are right!, i share my polyfill with you and create a PR to add this in react-native its very useful

Hey @bluebill1049 I add this polyfill

//App.tsx
if (typeof navigator != 'undefined' && typeof Proxy == 'undefined') {
  //@ts-ignore
  global.Proxy = require('es6-proxy-polyfill');
}

However i need change this line in source code: https://github.com/react-hook-form/react-hook-form/blob/master/src/useForm.ts#L120

with this:

diff --git a/node_modules/react-hook-form/dist/react-hook-form.js b/node_modules/react-hook-form/dist/react-hook-form.js
index 62d2b67..0f788f6 100644
--- a/node_modules/react-hook-form/dist/react-hook-form.js
+++ b/node_modules/react-hook-form/dist/react-hook-form.js
@@ -696,7 +696,7 @@ function useForm({ mode = VALIDATION_MODE.onSubmit, reValidateMode = VALIDATION_
     const isWeb = typeof document !== UNDEFINED &&
         !isWindowUndefined &&
         !isUndefined(window.HTMLElement);
-    const isProxyEnabled = isWeb && 'Proxy' in window;
+    const isProxyEnabled = (isWeb && 'Proxy' in window) || (!isUndefined(navigator) && !isUndefined(Proxy));
     const readFormStateRef = React.useRef({
         dirty: !isProxyEnabled,
         dirtyFields: !isProxyEnabled,

What about the following? less code (if it works)

const isProxyEnabled = (isWeb && 'Proxy' in window) || (!isWeb && Proxy);

What about the following? less code (if it works)

const isProxyEnabled = (isWeb && 'Proxy' in window) || (!isWeb && Proxy);

mmm i think add navigator, because Nodejs have proxy(last versions) and with SSR this is good idea?

What about the following? less code (if it works)
const isProxyEnabled = (isWeb && 'Proxy' in window) || (!isWeb && Proxy);

mmm i think add navigator, because Nodejs have proxy(last versions) and with SSR this is good idea?

good thoughts, I think should be fine. we don't capture formState in SSR

Thanks, sorry i dont have time to review website and add this polyfill :(, however need modify source code to change isProxyEnabled, maybe you add this in next version?

no worries @punisher97 i will take care the docs, you can submit a PR around this 馃檹 thanks

@bluebill1049 the last thing and my last suggestion, in react-native check in ValidationResolver if mounted, because, in strangly case, this its triggered and freeze the app if have many fields or heavy validationSchema.

  const isMounted = React.useRef<boolean | undefined>(false);
  React.useEffect(() => {
    isMounted.current = true;
    return () => {
      isMounted.current = false;
    };
  }, []);

  return React.useCallback<ValidationResolver<T>>(
    async (data, validationContext) => {
      if (!isMounted.current) {
        return {
          values: data,
          errors: {},
        };
      }

it shouldn't freeze once you have proxy setup, because isValid form state will no longer will be checked when inputs mounted (if you are not reading them).

it shouldn't freeze once you have proxy setup, because isValid form state will no longer will be checked when inputs mounted (if you are not reading them).

yes, sorry maybe the app not refresh when i testing with polyfill, but i testing polyfill now, and working perfectly

awesome, looking forward to your PR, we can roll this out in the next release.

Was this page helpful?
0 / 5 - 0 ratings