andyrichardson / fielder

A field-first form library for React and React Native
https://fielder.andyrichardson.dev
MIT License
196 stars 10 forks source link

Broken on newest React Native? #294

Closed rgalanakis closed 3 years ago

rgalanakis commented 3 years ago

Hi, I'm trying to get this library working, but unfortunately in my Expo project with React Native 0.61 and React Navigation, I'm getting an error as soon as I add a validate flag to field: Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

A simple example:

import React from "react";
import { FielderProvider, useField, useForm } from "fielder";

export default function SignInScreen({ navigation }) {
  return <Inner navigation={navigation} />
}

function Inner() {
  console.log('rendering inner')
  return <Inner2 />
}

function Inner2() {
  console.log('rendering inner2')
  const myForm = useForm();
  return (
        <FielderProvider value={myForm}>
          <Inner3/>
        </FielderProvider>
  );
}

function Inner3() {
  return <MyInput />
}

function MyInput() {
  useField({
    name: 'somefield',
    validate: () => null,
  });
  return null;
}

This should be on a React Navigation navigator. It logs 'rendering inner2' a bunch of times then errors. I verified the props are empty are on every render. And to confirm- if I remove the validate: line inside MyInput, the issue does not manifest. Also, the validation function is never called.

I'd love to use this library and will take a look to see if I can figure it out myself, but hopefully I'm doing something wrong.

rgalanakis commented 3 years ago

AFAICT, this is calling SET_FIELD_STATE over and over. I eventually had to give up figuring out what's up (was working from a transpiled and hand-restored copy). I wonder if there's some sort of equality function in a hook that is valid (compares equal) for react-web and compares unequal for React Native. So there is a side effect on every render, calling the state/reducer to change.

andyrichardson commented 3 years ago

Hey @rgalanakis thanks for the report - this slipped under my radar 👍

The issue is due to your validate function. Each render, you construct a new validation function, which in turn, triggers an update on field validation.

Here are a couple of ways you can get around this:

Pull the function out of the component

function MyInput() {
  useField({
    name: 'somefield',
    validate: validateFn,
  });
  return null;
}

const validateFn = () => null

Memoize the validation function


function MyInput() {
  useField({
    name: 'somefield',
    validate: useCallback(() => null, []),
  });
  return null;
}
rgalanakis commented 3 years ago

Oh, interesting, thanks Andy! I wonder if there is any way to detect this in the library as IME something like () => null is normally safe even if it's supposed to be in a useCallback.

andyrichardson commented 3 years ago

@rgalanakis unfortunately not - this is just one of those things with React hooks.

The positive side of this is that Fielder will respond to validation function changes - even if they're dependent on external (non form) state.

useField({
  name: 'someField',
  validate: useCallback(() => {
    if (someState) {
      // ...
    }
  }, [someState]),
})
rgalanakis commented 3 years ago

That totally makes sense. Thanks Andy!