Search code examples
javascriptreactjsclosures

How to prevent state from resetting when calling a state setter in a 3rd party library


I'm trying to convert a React class component into a React function component.

There is an onChange function that is called both inside the component and from outside. When calling a function component function, the useState hook takes the initial value. When I'm using the old class component way, everything works correctly. Why is this happening, and how to solve this problem?

const MyInput = (props) => {
  const { someLib, ...otherProps } = props;
  const [test, setTest] = useState(1); // console show 1,2,3, etc
            
  useEffect(() => {
    someLib && someLib.addCallback(onChange);
  }, []);

  const onChange = (event) => { 
    setTest(test + 1) // this function can called inside MyInput, and from someLib, 
                      // when it called from someLib, 'test' is reset, but in class component everything good 
  }
}

Solution

  • The problem is that onChange is a stale closure. What you need to do is make onChange look like this:

    const onChange = (event) => {
        setTest(oldTest => oldTest +1)
    }
    

    Alternatively you could add test to the dependency array in your useEffect make sure to do cleanup though. (You should be doing this anyways but it is even more important now)

    useEffect(() => {
        someLib && someLib.addCallback(onChange);
    
        return () => {
            someLib.removeCallback(onChange);
        }
    }, [someLib, test]);
    

    technically if you are doing this latter method you would want to useCallback

    const onChange = useCallback((event) => {
        setTest(test + 1);
    }, [test]);
    
    useEffect(() => {
        someLib && someLib.addCallback(onChange);
    
        return () => {
            someLib.removeCallback(onChange);
        }
    }, [someLib, onChange]);
    

    the benefit of this is that you don't have to keep track of the dependencies of onChange in a different place. The dependency list for onChange is now close.