¿Cómo comparar oldValues y newValues en React Hooks useEffect?
Digamos que tengo 3 entradas: tasa, monto de envío y monto de recepción. Puse esas 3 entradas en los parámetros de diferenciación useEffect. Las reglas son:
- Si sendAmount cambió, calculo
receiveAmount = sendAmount * rate
- Si la cantidad recibida cambió, calculo
sendAmount = receiveAmount / rate
- Si la tarifa cambió, calculo
receiveAmount = sendAmount * rate
cuandosendAmount > 0
o calculosendAmount = receiveAmount / rate
cuandoreceiveAmount > 0
Aquí está el codesandbox https://codesandbox.io/s/pkl6vn7x6j para demostrar el problema.
¿Hay alguna manera de comparar oldValues
y newValues
me gusta componentDidUpdate
en lugar de crear 3 controladores para este caso?
Gracias
Aquí está mi solución final con usePrevious
https://codesandbox.io/s/30n01w2r06
En este caso, no puedo usar varios useEffect
porque cada cambio conduce a la misma llamada de red. Por eso también suelo realizar changeCount
un seguimiento del cambio. Esto changeCount
también es útil para realizar un seguimiento de los cambios solo desde el local, de modo que pueda evitar llamadas de red innecesarias debido a cambios del servidor.
Puede escribir un gancho personalizado para proporcionarle accesorios anteriores usando useRef
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
y luego usarlo enuseEffect
const Component = (props) => {
const {receiveAmount, sendAmount } = props
const prevAmount = usePrevious({receiveAmount, sendAmount});
useEffect(() => {
if(prevAmount.receiveAmount !== receiveAmount) {
// process here
}
if(prevAmount.sendAmount !== sendAmount) {
// process here
}
}, [receiveAmount, sendAmount])
}
Sin embargo, es más claro y probablemente mejor y más claro de leer y comprender si usa dos useEffect
por separado para cada ID de cambio y desea procesarlos por separado.
En caso de que alguien esté buscando una versión TypeScript de usePrevious
:
En un .tsx
módulo:
import { useEffect, useRef } from "react";
const usePrevious = <T extends unknown>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
O en un .ts
módulo:
import { useEffect, useRef } from "react";
const usePrevious = <T>(value: T): T | undefined => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
Opción 1: ejecutar useEffect cuando el valor cambie
const Component = (props) => {
useEffect(() => {
console.log("val1 has changed");
}, [val1]);
return <div>...</div>;
};
Manifestación
Opción 2: use el gancho HasChanged
Comparar un valor actual con un valor anterior es un patrón común y justifica un gancho personalizado propio que oculta los detalles de implementación.
const Component = (props) => {
const hasVal1Changed = useHasChanged(val1)
useEffect(() => {
if (hasVal1Changed ) {
console.log("val1 has changed");
}
});
return <div>...</div>;
};
const useHasChanged= (val: any) => {
const prevVal = usePrevious(val)
return prevVal !== val
}
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Manifestación
Saliendo de la respuesta aceptada, una solución alternativa que no requiere un gancho personalizado:
const Component = ({ receiveAmount, sendAmount }) => {
const prevAmount = useRef({ receiveAmount, sendAmount }).current;
useEffect(() => {
if (prevAmount.receiveAmount !== receiveAmount) {
// process here
}
if (prevAmount.sendAmount !== sendAmount) {
// process here
}
return () => {
prevAmount.receiveAmount = receiveAmount;
prevAmount.sendAmount = sendAmount;
};
}, [receiveAmount, sendAmount]);
};
Esto supone que realmente necesita una referencia a los valores anteriores para cualquier cosa en los bits de "procesar aquí". De lo contrario, a menos que sus condicionales estén más allá de una !==
comparación directa, la solución más simple aquí sería simplemente:
const Component = ({ receiveAmount, sendAmount }) => {
useEffect(() => {
// process here
}, [receiveAmount]);
useEffect(() => {
// process here
}, [sendAmount]);
};
Acabo de publicar reaccionar-delta que resuelve exactamente este tipo de escenario. En mi opinión, useEffect
tiene demasiadas responsabilidades.
Responsabilidades
- Compara todos los valores en su matriz de dependencia usando
Object.is
- Ejecuta devoluciones de llamadas de efecto/limpieza basadas en el resultado del n.° 1
Rompiendo responsabilidades
react-delta
divide useEffect
las responsabilidades en varios ganchos más pequeños.
Responsabilidad #1
usePrevious(value)
useLatest(value)
useDelta(value, options)
useDeltaArray(valueArray, options)
useDeltaObject(valueObject, options)
some(deltaArray)
every(deltaArray)
Responsabilidad #2
useConditionalEffect(callback, boolean)
En mi experiencia, este enfoque es más flexible, limpio y conciso que useEffect
/ useRef
soluciones.