web-dev-qa-db-fra.com

Avec useEffect, comment puis-je ignorer l'application d'un effet sur le rendu initial?

Avec les nouveaux crochets d'effet de React, je peux dire à React de ne pas appliquer un effet si certaines valeurs n'ont pas changé entre les rendus, par exemple - Exemple tiré de la documentation de React:

useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]); // Only re-run the effect if count changes

Mais l'exemple ci-dessus applique l'effet sur le rendu initial et sur les rendus ultérieurs où count a changé. Comment puis-je dire React pour ignorer l'effet sur le rendu initial?)

19
uturnr

Comme l'indique le guide,

Le crochet d'effet, useEffect, ajoute la possibilité d'exécuter des effets secondaires à partir d'un composant de fonction. Il remplit les mêmes fonctions que composantDidMount, composantDidUpdate et composantWillUnmount dans les classes React, mais unifiées dans une API unique.

Dans cet exemple tiré du guide, il est attendu que count ne soit égal à 0 que lors du rendu initial:

const [count, setCount] = useState(0);

Donc cela fonctionnera comme componentDidUpdate avec un contrôle supplémentaire:

useEffect(() => {
  if (count)
    document.title = `You clicked ${count} times`;
}, [count]);

Voici en gros comment utiliser un hook personnalisé à la place de useEffect:

function useDidUpdateEffect(fn, inputs) {
  const didMountRef = useRef(false);

  useEffect(() => {
    if (didMountRef.current)
      fn();
    else
      didMountRef.current = true;
  }, inputs);
}

Les crédits vont à @Tholle pour avoir suggéré useRef au lieu de setState.

40
Estus Flask

J'utilise une variable d'état régulière au lieu d'un ref.

// Initializing didMount as false
const [didMount, setDidMount] = useState(false)

// Setting didMount to true upon mounting
useEffect(() => setDidMount(true), [])

// Now that we have a variable that tells us wether or not the component has
// mounted we can change the behavior of the other effect based on that
const [count, setCount] = useState(0)
useEffect(() => {
  if (didMount) document.title = `You clicked ${count} times`
}, [count])

Nous pouvons reformuler la logique didMount en tant que crochet personnalisé, comme ceci.

function useDidMount() {
  const [didMount, setDidMount] = useState(false)
  useEffect(() => setDidMount(true), [])

  return didMount
}

Enfin, nous pouvons l’utiliser dans notre composant comme celui-ci.

const didMount = useDidMount()

const [count, setCount] = useState(0)
useEffect(() => {
  if (didMount) document.title = `You clicked ${count} times`
}, [count])
5
Amiratak88

Voici un crochet personnalisé qui fournit simplement un indicateur booléen pour indiquer si le rendu actuel est le premier rendu (lorsque le composant a été monté). C'est à peu près la même chose que certaines des autres réponses, mais vous pouvez utiliser l'indicateur dans un useEffect ou la fonction de rendu, ou n'importe où dans le composant de votre choix. Peut-être que quelqu'un peut proposer un meilleur nom.

import { useRef, useEffect } from 'react';

export const useIsMount = () => {
  const isMountRef = useRef(true);
  useEffect(() => {
    isMountRef.current = false;
  }, []);
  return isMountRef.current;
};

Vous pouvez l'utiliser comme:

import React, { useEffect } from 'react';

import { useIsMount } from './useIsMount';

const MyComponent = () => {
  const isMount = useIsMount();

  useEffect(() => {
    if (isMount) {
      console.log('First Render');
    } else {
      console.log('Subsequent Render');
    }
  });

  return isMount ? <p>First Render</p> : <p>Subsequent Render</p>;
};

Et voici un test si vous êtes intéressé:

import { renderHook } from 'react-hooks-testing-library';

import { useIsMount } from '../useIsMount';

describe('useIsMount', () => {
  it('should be true on first render and false after', () => {
    const { result, rerender } = renderHook(() => useIsMount());
    expect(result.current).toEqual(true);
    rerender();
    expect(result.current).toEqual(false);
    rerender();
    expect(result.current).toEqual(false);
  });
});

Notre cas d'utilisation était de masquer des éléments animés si les accessoires initiaux indiquaient qu'ils devaient être masqués. Si les accessoires changent ultérieurement, nous voulons que les éléments s'animent.

3
Scotty Waggoner

L'approche @estus est acceptable, mais la plupart du temps, je simplifie les choses, sans avoir besoin d'utiliser des références.

Le point clé est que vous utilisez la plupart du temps useEffect pour extraire ou calculer des données/composants, de sorte que vous pouvez simplement renvoyer false (rien ne sera rendu) ou charger spinner au premier rendu et après l'extraction/calculs (useEffect) - composant de rendu.

function nicknameComponent({reader}) {
  const [nickname, nicknameSet] = useState(false)

  useEffect(() => {
    let res = await fetch('api/reader')
    res = await res.json()
    nicknameSet(res)
  }, [reader]);

  if (!nickname) return false
  //or show loading spinner
  //if (!nickname) return <LoadingSpinner>

  return <div>{nickname}</div>
}
1
RTW