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 })
.
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,
};
});
};