Hay algunos datos provenientes de un sondeo largo cada 5 segundos y me gustaría que mi componente envíe una acción cada vez que cambia un elemento de una matriz (o la longitud de la matriz). ¿Cómo evito que useEffect entre en un bucle infinito cuando paso una matriz como dependencia a useEffect pero aún logro enviar alguna acción si cambia algún valor?
useEffect(() => { console.log(outcomes) }, [outcomes]) donde outcomes son una matriz de ID, como [123, 234, 3212] . Los elementos en la matriz pueden reemplazarse o eliminarse, por lo que la longitud total de la matriz podría permanecer igual, pero no tiene por qué hacerlo, por lo que pasar outcomes.length como dependencia no es el caso.
los outcomes provienen del selector personalizado de reseleccionar:
const getOutcomes = createSelector( someData, data => data.map(({ outcomeId }) => outcomeId) )Otra opción de ES6 sería usar literales de plantilla para convertirlo en una cadena. Similar a JSON.stringify() , excepto que el resultado no estará envuelto en []
useEffect(() => { console.log(outcomes) }, [`${outcomes}`])Otra opción, si el tamaño de la matriz no cambia , sería distribuirlo en:
useEffect(() => { console.log(outcomes) }, [ ...outcomes ])El uso JSON.stringify() o cualquier método de comparación profundo puede ser ineficiente, si conoce de antemano la forma del objeto, puede escribir su propio gancho de efecto que activa la devolución de llamada en función del resultado de su función de igualdad personalizada.
useEffect funciona al verificar si cada valor en la matriz de dependencias es la misma instancia que la del renderizado anterior y ejecuta la devolución de llamada si uno de ellos no lo es. Por lo tanto, solo debemos mantener la instancia de los datos que nos interesa usar useRef y solo asignar uno nuevo si la verificación de igualdad personalizada devuelve false para activar el efecto.
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<number[]>(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 <pre>{JSON.stringify(arr)}</pre>; }Yendo un paso más allá, también podemos reutilizar el gancho creando un gancho de efecto que acepte una función de igualdad personalizada.
type MaybeCleanUpFn = void | (() => void); type EqualityFn = (a: DependencyList, b: DependencyList) => boolean; function useCustomEffect( cb: () => MaybeCleanUpFn, deps: DependencyList, equal?: EqualityFn ) { const ref = useRef<DependencyList>(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]) );