Search code examples
reactjstypescriptreact-nativereact-hooks

React-Native run a method to update one state by listening to another state


I am trying to run a method when a specific state changes. However I get 'maximum update depth reached'. This does not make sense since the function will be listening on the changes from stateA to update stateB. I have tried to do something like setState(prev => { ...prev, new }). However, I need to do more than just concat the new data to the previous data.

Here is the function I need to run:

const _updateGraph = (_state: { x: any; y: any; z: any; }) => {
  let X = parseFloat("" + _state.x.toFixed(3));
  let Y = parseFloat("" + _state.y.toFixed(3));
  let Z = parseFloat("" + _state.z.toFixed(3));
  let counter = graphState.counter;
  let stateDataX: { x: number, y: number }[] = graphState.dataX;
  let stateDataY: { x: number, y: number }[] = graphState.dataY;
  let stateDataZ: { x: number, y: number }[] = graphState.dataZ;

  setGraphState({
    dataX: stateDataX.concat({ x: counter, y: X }),
    dataY: stateDataY.concat({ x: counter, y: Y }),
    dataZ: stateDataZ.concat({ x: counter, y: Z }),
    dataObj: { x: X, y: Y, z: Z },
    counter: counter + 1
  });
};

The hook I was trying to use:

useEffect(() => { 
  _updateGraph({ x, y, z }); 
}, [{ x, y, z }]);

The useEffect is listening on the updates from { x, y, z } in order to update graphState. How does that cause an infinite loop?

The only think I can think of trying is to rewrite the update function, so that I can use setState like setState(prev => { ...prev, new }).


Solution

  • React hook's dependency arrays use a shallow reference equality check, and you are passing a new object reference each render cycle with { x, y, z } as a dependency. This causes the effect to re-run each render cycle.

    console.log({} === {}); // false
    console.log({ x: 1, y: 2, z: 3 } === { x: 1, y: 2, z: 3 }); // false

    Either pass x, y, and z individually as dependencies:

    useEffect(() => { 
      _updateGraph({ x, y, z }); 
    }, [x, y, z]);
    

    Or memoize the object reference with useMemo hook first:

    const coord3d = React.useMemo(() => ({ x, y, z }), [x, y, z]);
    
    useEffect(() => { 
      _updateGraph(coord3d); 
    }, [coord3d]);
    

    Additionally, any time you are enqueueing a state update and the next state value depends on the current state value you should try to use a functional state update.

    Example:

    const _updateGraph = (_state: { x: any; y: any; z: any; }) => {
      const x = parseFloat("" + _state.x.toFixed(3));
      const y = parseFloat("" + _state.y.toFixed(3));
      const z = parseFloat("" + _state.z.toFixed(3));
    
      setGraphState(graphState => {
        return {
          dataX: graphState.dataX.concat({ x: counter, y: x }),
          dataY: graphState.dataY.concat({ x: counter, y: y }),
          dataZ: graphState.dataZ.concat({ x: counter, y: z }),
          dataObj: { x, y, z },
          counter: graphState.counter + 1,
        };
      });
    };