web-dev-qa-db-fra.com

`useState` ne met à jour le composant que lorsque les valeurs de l'objet changent

Problème

useState déclenche toujours une mise à jour même lorsque les valeurs des données n'ont pas changé.

Voici une démo fonctionnelle du problème: démo

Contexte

J'utilise le crochet useState pour mettre à jour un objet et j'essaie de le faire mettre à jour uniquement lorsque les valeurs de cet objet changent. Parce que React utilise l'algorithme de comparaison Object.is pour déterminer quand il doit être mis à jour; les objets avec des valeurs équivalentes entraînent toujours le nouveau rendu du composant car ce sont des objets différents .

Ex. Ce composant sera toujours rendu même si la valeur de la charge utile reste comme { foo: 'bar' }

const UseStateWithNewObject = () => {
  const [payload, setPayload] = useState({});
  useEffect(
    () => {
      setInterval(() => {
        setPayload({ foo: 'bar' });
      }, 500);
    },
    [setPayload]
  );

  renderCountNewObject += 1;
  return <h3>A new object, even with the same values, will always cause a render: {renderCountNewObject}</h3>;
};

Question

Y a-t-il un moyen pour que je puisse implémenter quelque chose comme shouldComponentUpdate avec des crochets pour dire à réagir de ne re-rendre mon composant que lorsque les données changent?

8
agconti

Dans le cas où vous vérifiez les modifications sur les accessoires de type objet ou tableau d'objets, je suggère d'utiliser JSON.stringify sur useEffect deps. C'est assez simple.

const obj = {foo: 'bar'}
const [state, setState] = useState(obj)

useEffect(() => {
    setState(obj)
},[JSON.stringify(obj)])
0
lemospy

La solution générique à cela, qui n'implique pas l'ajout de logique à vos effets, consiste à diviser vos composants en:

  • conteneur incontrôlé avec un état qui rend ...
  • composant sans état contrôlé muet qui a été mémorisé avec React.memo

Votre composant stupide peut être pur (comme s'il avait shouldComponentUpdate implémenté et votre composant de gestion d'état intelligent peut être "stupide" et ne pas s'inquiéter de la mise à jour de l'état à la même valeur.

Exemple:

Avant

export default function Foo() {
  const [state, setState] = useState({ foo: "1" })
  const handler = useCallback(newValue => setState({ foo: newValue }))

  return (
    <div>
      <SomeWidget onEvent={handler} />
      Value: {{ state.foo }}
    </div>
  )

Après

const FooChild = React.memo(({foo, handler}) => {
  return (
    <div>
      <SomeWidget onEvent={handler} />
      Value: {{ state.foo }}
    </div>
  )
})

export default function Foo() {
  const [state, setState] = useState({ foo: "1" })
  const handler = useCallback(newValue => setState({ foo: newValue }))

  return <FooChild handler={handler} foo={state.foo} />
}

Cela vous donne la séparation de la logique que vous recherchez.

0
Brandon