web-dev-qa-db-fra.com

Comment comparer oldValues ​​et newValues ​​sur React Hooks useEffect?

Disons que j'ai 3 entrées: rate, sendAmount et receiveAmount. J'ai mis cela 3 entrées sur useEffect diffing params. Les règles sont:

  • Si sendAmount a changé, je calcule receiveAmount = sendAmount * rate
  • Si receiveAmount a changé, je calcule sendAmount = receiveAmount / rate
  • Si le taux change, je calcule receiveAmount = sendAmount * rate lorsque sendAmount > 0 ou je calcule sendAmount = receiveAmount / rate lorsque receiveAmount > 0

Voici les codesandbox https://codesandbox.io/s/pkl6vn7x6j pour illustrer le problème.

Existe-t-il un moyen de comparer les oldValues et newValues comme sur componentDidUpdate au lieu de créer 3 gestionnaires pour ce cas?

Merci


Voici ma solution finale avec usePrevioushttps://codesandbox.io/s/30n01w2r06

Dans ce cas, je ne peux pas utiliser plusieurs useEffect, car chaque modification conduit au même appel réseau. C'est pourquoi j'utilise aussi changeCount pour suivre le changement. Cette changeCount est également utile pour suivre les modifications apportées uniquement en local, afin d'éviter des appels réseau inutiles en raison de modifications apportées par le serveur.

42
rwinzhang

Vous pouvez écrire un crochet personnalisé pour vous fournir un accessoires précédents utilisant useRef

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
}

puis l'utiliser dans useEffect

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])
}

Cependant, sa lecture et sa compréhension sont plus claires et probablement meilleures et plus claires si vous utilisez deux useEffect séparément pour chaque identificateur de modification que vous souhaitez traiter séparément.

62
Shubham Khatri

Option 1 - crochet d'utilisation

Comparer une valeur actuelle à une valeur précédente est un modèle courant et justifie un crochet personnalisé qui cache les détails de la mise en œuvre.

const useCompare = (val: any) => {
    const prevVal = usePrevious(val)
    return prevVal !== val
}

const usePrevious = (value) {
    const ref = useRef();
    useEffect(() => {
      ref.current = value;
    });
    return ref.current;
}

const Component = (props) => {
  ...
  const hasVal1Changed = useCompare(val1)
  const hasVal2Changed = useCompare(val2);
  useEffect(() => {
    if (hasVal1Changed ) {
      console.log("val1 has changed");
    }
    if (hasVal2Changed ) {
      console.log("val2 has changed");
    }
  });

  return <div>...</div>;
};

démo

Option 2 - lancer useEffect lorsque la valeur change

const Component = (props) => {
  ...
  useEffect(() => {
    console.log("val1 has changed");
  }, [val1]);
  useEffect(() => {
    console.log("val2 has changed");
  }, [val2]);

  return <div>...</div>;
};

démo

6
Ben Carp

Etant donné que l'état n'est pas étroitement associé à l'instance de composant dans les composants fonctionnels, l'état précédent ne peut pas être atteint dans useEffect sans le sauvegarder au préalable, par exemple avec useRef. Cela signifie également que la mise à jour d'état a peut-être été implémentée de manière incorrecte au mauvais endroit car l'état précédent est disponible dans la fonction setState Updater.

C'est un bon cas d'utilisation pour useReducer qui fournit un stockage de type Redux et permet d'implémenter un modèle respectif. Les mises à jour d'état sont effectuées explicitement. Il n'est donc pas nécessaire de déterminer quelle propriété d'état est mise à jour. cela ressort déjà de l'action expédiée.

Voici un exemple à quoi il pourrait ressembler:

function reducer({ sendAmount, receiveAmount, rate }, action) {
  switch (action.type) {
    case "sendAmount":
      sendAmount = action.payload;
      return {
        sendAmount,
        receiveAmount: sendAmount * rate,
        rate
      };
    case "receiveAmount":
      receiveAmount = action.payload;
      return {
        sendAmount: receiveAmount / rate,
        receiveAmount,
        rate
      };
    case "rate":
      rate = action.payload;
      return {
        sendAmount: receiveAmount ? receiveAmount / rate : sendAmount,
        receiveAmount: sendAmount ? sendAmount * rate : receiveAmount,
        rate
      };
    default:
      throw new Error();
  }
}

function handleChange(e) {
  const { name, value } = e.target;
  dispatch({
    type: name,
    payload: value
  });
}

...
const [state, dispatch] = useReducer(reducer, {
  rate: 2,
  sendAmount: 0,
  receiveAmount: 0
});
...
1
Estus Flask

Si n'importe qui est à la recherche d'une version TypeScript de usePrevious:

import { useEffect, useRef } from "react";

const usePrevious = <T>(value: T) => {
  const ref = useRef<T>();
  useEffect(() => {
    ref.current = value;
  });
  return ref.current;
};
0
SeedyROM