134 votes

Avec useEffect, comment puis-je éviter d'appliquer un effet lors du 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 deux présentations - 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 d'ignorer l'effet lors du rendu initial ?

2 votes

Avez-vous regardé dans React.useMemo ?

174voto

estus Points 5252

Comme l'indique le guide,

Le crochet d'effet, useEffect, ajoute la possibilité d'effectuer des effets secondaires à partir d'un composant de fonction. Il sert le même objectif que componentDidMount, componentDidUpdate et componentWillUnmount dans les classes React, mais unifié en une seule API.

Dans cet exemple du guide, il est prévu que count est 0 uniquement lors du rendu initial :

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

Cela fonctionnera donc comme suit componentDidUpdate avec un chèque supplémentaire :

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

Il s'agit essentiellement d'un hook personnalisé qui peut être utilisé à la place de useEffect peut fonctionner :

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

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

Merci à @Tholle pour la suggestion. useRef au lieu de setState .

5 votes

Où est la suggestion de useRef sur setState ? Je ne le vois pas sur cette page et j'aimerais comprendre pourquoi.

9 votes

@rob-gordon Il s'agit d'un commentaire qui a été supprimé après la mise à jour de la réponse. Le raisonnement est que useState entraînerait une mise à jour inutile du composant.

2 votes

Cette approche fonctionne, mais elle enfreint la règle du linter react-hooks/exhaustive-deps. C'est-à-dire que fn n'est pas donné dans le tableau des deps. Quelqu'un a-t-il une approche qui ne viole pas les meilleures pratiques de React ?

76voto

OzzieOrca Points 375

Voici un hook personnalisé qui fournit simplement un drapeau 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 le drapeau dans un fichier de type useEffect ou la fonction de rendu ou n'importe où ailleurs dans le composant que vous voulez. 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 ça :

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 pour ça si vous êtes intéressés :

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

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 cacher les éléments animés si les props initiaux indiquent qu'ils doivent être cachés. Sur les rendus ultérieurs, si les accessoires ont changé, nous voulions que les éléments soient animés.

0 votes

L'importation est passée de react-hooks-testing-library à @testing-library/react-hooks github.com/testing-library/react-hooks-testing-library/releases/

2 votes

Pourquoi avez-vous choisi "isMount" et non "didMount" ou "isMounted" ?

4 votes

Je pense que je me demandais si le rendu actuel était le rendu où le montage avait lieu. Oui, je suis d'accord, ça fait un peu bizarre d'y revenir maintenant. Mais c'est vrai sur le premier rendu et faux après, donc tes suggestions semblent trompeuses. isFirstRender pourrait fonctionner.

30voto

Vencovsky Points 7756

J'ai trouvé une solution plus simple et qui ne nécessite pas l'utilisation d'un autre crochet, mais elle présente des inconvénients.

useEffect(() => {
  // skip initial render
  return () => {
    // do something with dependency
  }
}, [dependency])

Ce n'est qu'un exemple qui montre qu'il existe d'autres façons de procéder si votre cas est très simple.

L'inconvénient de cette méthode est que vous ne pouvez pas avoir d'effet de nettoyage et qu'elle ne s'exécutera que lorsque le tableau de dépendances changera pour la deuxième fois.

Il n'est pas recommandé de l'utiliser et vous devriez utiliser ce que les autres réponses disent, mais je l'ai seulement ajouté ici pour que les gens sachent qu'il y a plus d'une façon de le faire.

Edit :

Pour que ce soit plus clair, vous ne devriez pas utiliser ceci Vous pouvez utiliser une approche différente pour résoudre le problème de la question (en sautant le rendu initial), ceci uniquement à des fins pédagogiques pour montrer que vous pouvez faire la même chose de différentes manières. Si vous devez sauter le rendu initial, veuillez utiliser cette approche pour d'autres réponses.

1 votes

Je viens d'apprendre quelque chose. Je ne pensais pas que ça allait marcher, puis j'ai essayé et ça marche. Merci !

2 votes

React devrait fournir un meilleur moyen pour cela, mais ce problème est ouvert sur Github et la proposition est d'écrire une solution personnalisée vous-même (ce qui est un non-sens total).

1 votes

Le problème avec celle-ci est qu'elle sera exécutée lors du démontage, ce qui n'est pas parfait.

25voto

Amiratak88 Points 536

J'utilise une variable d'état ordinaire au lieu d'une référence.

// 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 refactoriser la logique didMount comme un hook personnalisé comme ceci.

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

  return didMount
}

Enfin, nous pouvons l'utiliser dans notre composant comme ceci.

const didMount = useDidMount()

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

UPDATE Utilisation du hook useRef pour éviter le rerender supplémentaire (Merci à @TomEsterez pour la suggestion)

Cette fois, notre crochet personnalisé renvoie une fonction renvoyant la valeur actuelle de notre réf. Vous pouvez aussi utiliser la référence directement, mais je préfère cette méthode.

function useDidMount() {
  const mountRef = useRef(false);

  useEffect(() => { mountRef.current = true }, []);

  return () => mountRef.current;
}

Utilisation

const MyComponent = () => {
  const didMount = useDidMount();

  useEffect(() => {
    if (didMount()) // do something
    else // do something else
  })

  return (
    <div>something</div>
  );
}

En passant, je n'ai jamais eu à utiliser ce crochet et il y a probablement de meilleures façons de gérer cela, qui seraient plus conformes au modèle de programmation React.

13 votes

useRef est mieux adapté à cela car useState entraînera un rendu supplémentaire et inutile du composant : codesandbox.io/embed/youthful-goldberg-pz3cx

0 votes

Vous avez une erreur dans votre useDidMount Fonction. Vous devez encapsuler la mountRef.current = true en useEffect() avec des crochets { ...} . Les quitter, c'est comme écrire return mountRef.current = true et qui provoquent une erreur du type An effect function must not return anything besides a function, which is used for clean-up. You returned: true

8voto

Losses Don Points 1

Un hook compatible avec TypeScript et CRA, remplacez-le par useEffect ce crochet fonctionne comme suit useEffect mais ne sera pas déclenché pendant que le premier rendu a lieu.

import * as React from 'react'

export const useLazyEffect:typeof React.useEffect = (cb, dep) => {
  const initializeRef = React.useRef<boolean>(false)

  React.useEffect((...args) => {
    if (initializeRef.current) {
      cb(...args)
    } else {
      initializeRef.current = true
    }
  // eslint-disable-next-line react-hooks/exhaustive-deps
  }, dep)
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X