¿Cómo comparar oldValues ​​y newValues ​​en React Hooks useEffect?

Resuelto Erwin Zhang asked hace 6 años • 17 respuestas

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ó, calculoreceiveAmount = sendAmount * rate
  • Si la cantidad recibida cambió, calculosendAmount = receiveAmount / rate
  • Si la tarifa cambió, calculo receiveAmount = sendAmount * ratecuando sendAmount > 0o calculo sendAmount = receiveAmount / ratecuandoreceiveAmount > 0

Aquí está el codesandbox https://codesandbox.io/s/pkl6vn7x6j para demostrar el problema.

¿Hay alguna manera de comparar oldValuesy newValuesme gusta componentDidUpdateen 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 useEffectporque cada cambio conduce a la misma llamada de red. Por eso también suelo realizar changeCountun seguimiento del cambio. Esto changeCounttambié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.

Erwin Zhang avatar Nov 23 '18 18:11 Erwin Zhang
Aceptado

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 useEffectpor separado para cada ID de cambio y desea procesarlos por separado.

Shubham Khatri avatar Nov 23 '2018 12:11 Shubham Khatri

En caso de que alguien esté buscando una versión TypeScript de usePrevious:

En un .tsxmó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 .tsmódulo:

import { useEffect, useRef } from "react";

const usePrevious = <T>(value: T): T | undefined => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
SeedyROM avatar Aug 29 '2019 09:08 SeedyROM

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

Ben Carp avatar Jun 29 '2019 11:06 Ben Carp

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]);
};
Drazen Bjelovuk avatar Apr 08 '2020 20:04 Drazen Bjelovuk

Acabo de publicar reaccionar-delta que resuelve exactamente este tipo de escenario. En mi opinión, useEffecttiene demasiadas responsabilidades.

Responsabilidades

  1. Compara todos los valores en su matriz de dependencia usandoObject.is
  2. Ejecuta devoluciones de llamadas de efecto/limpieza basadas en el resultado del n.° 1

Rompiendo responsabilidades

react-deltadivide useEffectlas 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/ useRefsoluciones.

Austin Malerba avatar Dec 12 '2019 15:12 Austin Malerba