Comment corriger l'avertissement de dépendance manquante lors de l'utilisation de useEffect React Hook?

182

Avec React 16.8.6 (c'était bien sur la version précédente 16.8.3), j'obtiens cette erreur lorsque je tente d'empêcher une boucle infinie sur une requête de récupération

./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

J'ai été incapable de trouver une solution qui arrête la boucle infinie. Je veux éviter d'utiliser useReducer(). J'ai trouvé cette discussion https://github.com/facebook/react/issues/14920 où une solution possible est You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.que je ne suis pas sûr de ce que je fais donc je n'ai pas encore essayé de l'implémenter.

J'ai cette configuration actuelle React hook useEffect fonctionne en continu pour toujours / boucle infinie et le seul commentaire est useCallback()que je ne connais pas.

Comment j'utilise actuellement useEffect()(que je ne veux exécuter qu'une seule fois au début, similaire à componentDidMount())

useEffect(() => {
    fetchBusinesses();
  }, []);
const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
russ
la source

Réponses:

194

Si vous n'utilisez pas la méthode fetchBusinesses en dehors de l'effet, vous pouvez simplement la déplacer dans l'effet et éviter l'avertissement

useEffect(() => {
    const fetchBusinesses = () => {
       return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
  fetchBusinesses();
}, []);

Si toutefois vous utilisez fetchBusinesses en dehors du rendu, vous devez noter deux choses

  1. Y a-t-il un problème avec votre non- réussitefetchBusinesses tant que méthode lorsqu'elle est utilisée pendant le montage avec sa fermeture englobante?
  2. Votre méthode dépend-elle de certaines variables qu'elle reçoit de sa fermeture englobante? Ce n'est pas le cas pour vous.
  3. Sur chaque rendu, fetchBusinesses sera recréé et donc le transmettre à useEffect causera des problèmes. Donc, vous devez d'abord mémoriser fetchBusinesses si vous le transmettez au tableau de dépendances.

Pour résumer, je dirais que si vous utilisez en fetchBusinessesdehors de useEffectvous pouvez désactiver la règle en utilisant // eslint-disable-next-line react-hooks/exhaustive-depssinon vous pouvez déplacer la méthode à l'intérieur de useEffect

Pour désactiver la règle, vous l'écrivez comme

useEffect(() => {
   // other code
   ...

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Shubham Khatri
la source
14
J'ai bien utilisé la solution que vous avez décrite. Une autre solution que j'ai utilisée ailleurs en raison d'une configuration différente était useCallback(). Donc par exemple: const fetchBusinesses= useCallback(() => { ... }, [...]) et useEffect()cela ressemblerait à ceci:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
russ
1
@russ, vous avez raison, vous auriez besoin de mémoriser fetchBusiness en utilisant useCallback si vous voulez le passer au tableau de dépendances
Shubham Khatri
Ce serait bien si vous montriez où placer l'instruction eslint-disable. Je pensais que ce serait au-dessus de useEffect
user210757
1
utiliser // eslint-disable-next-line react-hooks/exhaustive-depspour expliquer au linter que votre code est correct est comme un hack. J'espère qu'ils trouveront une autre solution pour rendre le linter suffisamment intelligent pour détecter quand une dispute n'est pas obligatoire
Olivier Boissé
1
@TapasAdhikary, oui vous pouvez avoir une fonction asynchrone dans useEffect, il vous suffit de l'écrire différemment. Veuillez vérifier stackoverflow.com/questions/53332321/…
Shubham Khatri
77
./src/components/BusinessesList.js
Line 51:  React Hook useEffect has a missing dependency: 'fetchBusinesses'.
Either include it or remove the dependency array  react-hooks/exhaustive-deps

Ce n'est pas une erreur JS / React mais un avertissement eslint (eslint-plugin-react-hooks).

Cela vous indique que le hook dépend de la fonction fetchBusinesses, vous devez donc le passer en tant que dépendance.

useEffect(() => {
  fetchBusinesses();
}, [fetchBusinesses]);

Cela pourrait entraîner l'appel de la fonction à chaque rendu si la fonction est déclarée dans un composant comme:

const Component = () => {
  /*...*/

  //new function declaration every render
  const fetchBusinesses = () => {
    fetch('/api/businesses/')
      .then(...)
  }

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}

car chaque fonction de temps est redéclarée avec une nouvelle référence

La bonne façon de faire cela est:

const Component = () => {
  /*...*/

  // keep function reference
  const fetchBusinesses = useCallback(() => {
    fetch('/api/businesses/')
      .then(...)
  }, [/* additional dependencies */]) 

  useEffect(() => {
    fetchBusinesses();
  }, [fetchBusinesses]);

  /*...*/
}

ou simplement définir la fonction dans useEffect

En savoir plus: https://github.com/facebook/react/issues/14920

fard
la source
14
Cela entraîne une nouvelle erreurLine 20: The 'fetchBusinesses' function makes the dependencies of useEffect Hook (at line 51) change on every render. Move it inside the useEffect callback. Alternatively, wrap the 'fetchBusinesses' definition into its own useCallback() Hook
russ
1
la solution est bonne et si sur la fonction vous modifiez un autre état, vous devez ajouter les dépendances pour éviter un autre comportement inattendu
cesarlarsson
60

Vous pouvez le définir directement comme useEffectrappel:

useEffect(fetchBusinesses, [])

Il ne se déclenchera qu'une seule fois, alors assurez-vous que toutes les dépendances de la fonction sont correctement définies (comme pour l'utilisation componentDidMount/componentWillMount...)


Modifier 21/02/2020

Juste pour être complet:

1. Utilisez la fonction comme useEffectrappel (comme ci-dessus)

useEffect(fetchBusinesses, [])

2. Déclarez la fonction à l'intérieur useEffect()

useEffect(() => {
  function fetchBusinesses() {
    ...
  }
  fetchBusinesses()
}, [])

3. Mémorisez avec useCallback()

Dans ce cas, si vous avez des dépendances dans votre fonction, vous devrez les inclure dans le useCallbacktableau des dépendances et cela déclenchera à useEffectnouveau si les paramètres de la fonction changent. D'ailleurs, c'est beaucoup de passe-partout ... Il suffit donc de passer la fonction directement à useEffectas in 1. useEffect(fetchBusinesses, []).

const fetchBusinesses = useCallback(() => {
  ...
}, [])
useEffect(() => {
  fetchBusinesses()
}, [fetchBusinesses])

4. Désactivez l'avertissement d'eslint

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
jpenna
la source
2
Je t'aime ... Cette réponse est si complète!
Nick09
8

La solution est également donnée par react, ils vous conseillent d'utiliser useCallbackqui vous renverra une version mémoriser de votre fonction:

La fonction 'fetchBusinesses' fait changer les dépendances de useEffect Hook (à la ligne NN) à chaque rendu. Pour résoudre ce problème, enveloppez la définition de 'fetchBusinesses' dans sa propre utilisationCallback () Hook react-hooks / exhaust-deps

useCallbackest simple à utiliser car il a la même signature car useEffectla différence est que useCallback renvoie une fonction. Cela ressemblerait à ceci:

 const fetchBusinesses = useCallback( () => {
        return fetch("theURL", {method: "GET"}
    )
    .then(() => { /* some stuff */ })
    .catch(() => { /* some error handling */ })
  }, [/* deps */])
  // We have a first effect thant uses fetchBusinesses
  useEffect(() => {
    // do things and then fetchBusinesses
    fetchBusinesses(); 
  }, [fetchBusinesses]);
   // We can have many effect thant uses fetchBusinesses
  useEffect(() => {
    // do other things and then fetchBusinesses
    fetchBusinesses();
  }, [fetchBusinesses]);
Stéphane L
la source
1

Cet article est une bonne introduction à la récupération de données avec des hooks: https://www.robinwieruch.de/react-hooks-fetch-data/

Essentiellement, incluez la définition de la fonction fetch à l'intérieur useEffect:

useEffect(() => {
  const fetchBusinesses = () => {
    return fetch("theUrl"...
      // ...your fetch implementation
    );
  }

  fetchBusinesses();
}, []);
bonjourjoe
la source
1

Vous pouvez supprimer le 2ème tableau de type d'argument []mais le fetchBusinesses()sera également appelé à chaque mise à jour. Vous pouvez ajouter une IFinstruction dans l' fetchBusinesses()implémentation si vous le souhaitez.

React.useEffect(() => {
  fetchBusinesses();
});

L'autre consiste à implémenter la fetchBusinesses()fonction en dehors de votre composant. N'oubliez pas de transmettre des arguments de dépendance à votre fetchBusinesses(dependency)appel, le cas échéant.

function fetchBusinesses (fetch) {
  return fetch("theURL", { method: "GET" })
    .then(res => normalizeResponseErrors(res))
    .then(res => res.json())
    .then(rcvdBusinesses => {
      // some stuff
    })
    .catch(err => {
      // some error handling
    });
}

function YourComponent (props) {
  const { fetch } = props;

  React.useEffect(() => {
    fetchBusinesses(fetch);
  }, [fetch]);

  // ...
}
5 serviteur
la source
0

En fait, les avertissements sont très utiles lorsque vous développez avec des hooks. mais dans certains cas, cela peut vous aiguiller. surtout lorsque vous n'avez pas besoin d'écouter le changement des dépendances.

Si vous ne voulez pas mettre fetchBusinessesà l'intérieur des dépendances du hook, vous pouvez simplement le passer comme argument au callback du hook et définir le main fetchBusinessescomme valeur par défaut comme ceci

useEffect((fetchBusinesses = fetchBusinesses) => {
   fetchBusinesses();
}, []);

Ce n'est pas la meilleure pratique, mais cela pourrait être utile dans certains cas.

De plus, comme Shubnam l'a écrit, vous pouvez ajouter le code ci-dessous pour indiquer à ESLint d'ignorer la vérification de votre hook.

// eslint-disable-next-line react-hooks/exhaustive-deps
Behnam Azimi
la source
0

Je ne veux exécuter [ fetchBusinesses] qu'une seule fois au début, similaire àcomponentDidMount()

Vous pouvez retirer fetchBusinessescomplètement votre composant:

const fetchBusinesses = () => { // or pass some additional input from component as args
  return fetch("theURL", { method: "GET" }).then(n => process(n));
};

const Comp = () => {
  React.useEffect(() => {
    fetchBusinesses().then(someVal => {
      // ... do something with someVal
    });
  }, []); // eslint warning solved!
  return <div>{state}</div>;
};

Cela ne fournira pas seulement une solution simple et résoudra l'avertissement exhaustif deps. fetchBusinesspeut maintenant être testé mieux et faciliteComp , car il réside dans la portée du module en dehors de l'arborescence React.

Le déménagement à l' fetchBusinessesextérieur fonctionne bien ici, car nous ne pourrions lire que les accessoires initiaux et l'état du composant de toute façon en raison de la portée de fermeture périmée ( []dép dans useEffect).

Comment omettre les dépendances de fonction

  • Déplacer la fonction à l'intérieur de l'effet
  • Déplacer la fonction en dehors du composant - (nous utilisons celui-ci)
  • Appeler la fonction lors du rendu et laisser useEffectdépendre de cette valeur (fonction de calcul pure)
  • Ajoutez la fonction pour effectuer deps et enveloppez-la avec useCallback en dernier recours

Concernant les autres solutions:

Tirer à l' fetchBusinessesintérieur useEffect()n'aide pas vraiment, si vous accédez à un autre état. eslint se plaindrait toujours: Codesandbox .

Je voudrais également éviter d'eslint exhaustif-deps ignorer les commentaires. Il est juste trop facile de les oublier lorsque vous refactorisez et remaniez vos dépendances.

ford04
la source
0
const [mount, setMount] = useState(false)
const fetchBusinesses = () => { 
   //function defination
}
useEffect(() => {
   if(!mount) {
      setMount(true);
      fetchBusinesses();
   }
},[fetchBusinesses]);

Cette solution est assez simple et vous n'avez pas besoin de remplacer les avertissements es-lint. Maintenez simplement un indicateur pour vérifier si le composant est monté ou non.

Yasin
la source
0

vous essayez de cette façon

const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };

et

useEffect(() => {
    fetchBusinesses();
  });

c'est du travail pour vous. Mais ma suggestion est que cette méthode fonctionne également pour vous. C'est mieux qu'avant. J'utilise de cette façon:

useEffect(() => {
        const fetchBusinesses = () => {
    return fetch("theURL", {method: "GET"}
    )
      .then(res => normalizeResponseErrors(res))
      .then(res => {
        return res.json();
      })
      .then(rcvdBusinesses => {
        // some stuff
      })
      .catch(err => {
        // some error handling
      });
  };
        fetchBusinesses();
      }, []);

si vous obtenez des données sur la base d'un identifiant spécifique, ajoutez un callback useEffect [id]alors ne peut pas vous montrer d'avertissement React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array

Kashif
la source
-4

désactivez simplement eslint pour la ligne suivante;

useEffect(() => {
   fetchBusinesses();
// eslint-disable-next-line
}, []);

de cette façon, vous l'utilisez comme un composant l'a fait mount (appelé une fois)

actualisé

ou

const fetchBusinesses = useCallback(() => {
 // your logic in here
 }, [someDeps])

useEffect(() => {
   fetchBusinesses();
// no need to skip eslint warning
}, [fetchBusinesses]); 

fetchBusinesses sera appelé à chaque fois que certainsDeps changeront

user3550446
la source
au lieu de désactiver, faites simplement ceci: [fetchBusinesses]supprimera automatiquement l'avertissement et cela a résolu le problème pour moi.
rotimi-best
7
@RotimiBest - cela provoque un rendu infini comme décrit dans la question
user210757
En fait, je l'ai fait de cette façon dans l'un de mes projets il y a quelque temps et cela n'a pas produit une boucle infinie. Je vais vérifier à nouveau.
rotimi-best le
@ user210757 Attendez mais pourquoi cela provoquera-t-il une boucle infinie, ce n'est pas comme si vous définissiez l'état après avoir récupéré les données du serveur. Si vous mettiez à jour l'état, il vous suffit d'écrire une condition if avant d'appeler la fonction useEffectqui vérifie si l'état est vide.
rotimi-best le
@ rotimi-best a été ahwile depuis que j'ai commenté, mais je dirais que la fonction est recréée à chaque fois, donc jamais la même chose, elle sera toujours re-rendue, à moins que vous ne vous déplaciez dans le corps useEffect ou useCallback
user210757