Every 5 seconds there is some data coming from the long poll, and I want my component to dispatch an operation every time an item of the array (or the array length itself) changes. How do I prevent useEffect from going into an infinite loop when passing an array as a dependency, but still manage to schedule some operations if any value changes?
useEffect(() => { console.log(outcomes) }, [outcomes])
Where outcomes
is an ID array, such as [123, 234, 3212]
. Items in the array may be replaced or removed, so the total length of the array may (but does not necessarily) remain the same, so passing outcomes.length
as a dependency is not the case.
outcomes
Custom selector from reselect:
const getOutcomes = createSelector( someData, data => data.map(({ outcomeId }) => outcomeId) )
P粉4642089372023-10-18 15:49:11
Using JSON.stringify() or any deep comparison method may be less efficient, if you know the shape of the object ahead of time you can write your own effect hook to trigger a callback to your custom equality function based on the result.
useEffect
works by checking if each value in the dependencies array is the same as the value in the previous render and executing a callback if one of them is not. Therefore, we only need to use useRef
to retain the data instance we are interested in, and only allocate a new instance to trigger the effect when the custom equality check returns false
.
function arrayEqual(a1: any[], a2: any[]) { if (a1.length !== a2.length) return false; for (let i = 0; i < a1.length; i++) { if (a1[i] !== a2[i]) { return false; } } return true; } type MaybeCleanUpFn = void | (() => void); function useNumberArrayEffect(cb: () => MaybeCleanUpFn, deps: number[]) { const ref = useRef(deps); if (!arrayEqual(deps, ref.current)) { ref.current = deps; } useEffect(cb, [ref.current]); }
function Child({ arr }: { arr: number[] }) { useNumberArrayEffect(() => { console.log("run effect", JSON.stringify(arr)); }, arr); return{JSON.stringify(arr)}; }
Going a step further, we can also reuse this hook by creating an effects hook that accepts a custom equality function.
type MaybeCleanUpFn = void | (() => void); type EqualityFn = (a: DependencyList, b: DependencyList) => boolean; function useCustomEffect( cb: () => MaybeCleanUpFn, deps: DependencyList, equal?: EqualityFn ) { const ref = useRef(deps); if (!equal || !equal(deps, ref.current)) { ref.current = deps; } useEffect(cb, [ref.current]); }
useCustomEffect( () => { console.log("run custom effect", JSON.stringify(arr)); }, [arr], (a, b) => arrayEqual(a[0], b[0]) );
P粉6628028822023-10-18 12:32:21
You can pass JSON.stringify(outcomes)
as a list of dependencies:
Learn MoreHere
useEffect(() => { console.log(outcomes) }, [JSON.stringify(outcomes)])