Jak naprawić brakujące ostrzeżenie o zależnościach podczas korzystania z hooka reakcji useEffect?

175

W przypadku React 16.8.6 (było dobrze w poprzedniej wersji 16.8.3), pojawia się ten błąd, gdy próbuję zapobiec nieskończonej pętli w żądaniu pobierania

./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

Nie udało mi się znaleźć rozwiązania, które zatrzymałoby nieskończoną pętlę. Chcę trzymać się z daleka od używania useReducer(). Znalazłem tę dyskusję https://github.com/facebook/react/issues/14920, gdzie możliwym rozwiązaniem jest You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.brak pewności co do tego, co robię, więc jeszcze nie próbowałem go wdrożyć.

Mam tę bieżącą konfigurację React hook useEffect działa w sposób ciągły w nieskończoność / nieskończoną pętlę, a jedyny komentarz dotyczy tego, z useCallback()którym nie jestem zaznajomiony.

Jak obecnie używam useEffect()(które chcę uruchomić tylko raz na początku, podobnie jak 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
źródło

Odpowiedzi:

189

Jeśli nie używasz metody fetchBusinesses nigdzie poza efektem, możesz po prostu przenieść ją do efektu i uniknąć ostrzeżenia

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();
}, []);

Jeśli jednak używasz fetchBusinesses poza renderowaniem, musisz zwrócić uwagę na dwie rzeczy

  1. Czy jest jakiś problem z tym, że nie zdałeś?fetchBusinesses jako metoda, gdy jest używany podczas mocowanie jej zamknięcia otaczającej?
  2. Czy twoja metoda zależy od pewnych zmiennych, które otrzymuje z otaczającego ją zamknięcia? To nie dotyczy Ciebie.
  3. Przy każdym renderowaniu narzędzie fetchBusinesses zostanie utworzone ponownie, a zatem przekazanie go do metody useEffect spowoduje problemy. Więc najpierw musisz zapamiętać fetchBusinesses, jeśli chcesz przekazać to do tablicy zależności.

Podsumowując, powiedziałbym, że jeśli używasz fetchBusinessespoza useEffectsobą, możesz wyłączyć regułę, w // eslint-disable-next-line react-hooks/exhaustive-depsprzeciwnym razie możesz przenieść metodę wewnątrz useEffect

Aby wyłączyć regułę, napiszesz to jak

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

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Shubham Khatri
źródło
14
Użyłem rozwiązania, które ładnie opisałeś. Innym rozwiązaniem, którego użyłem w innym przypadku, było inne rozwiązanie useCallback(). Na przykład: const fetchBusinesses= useCallback(() => { ... }, [...]) a useEffect()wyglądałoby to tak:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
russ
1
@russ, masz rację, to musiałaby memoize fetchBusiness użyciu useCallback jeśli jesteś przekazać go do tablicy zależność
Shubham Khatri
Byłoby miło, gdybyś pokazał, gdzie umieścić instrukcję eslint-disable. Myślałem, że będzie powyżej useEffect
user210757
1
używanie // eslint-disable-next-line react-hooks/exhaustive-depsdo wyjaśnienia linterowi, że twój kod jest poprawny, jest jak włamanie. Spodziewam się, że znajdą inne rozwiązanie, które sprawi, że linter będzie wystarczająco
mądry
1
@TapasAdhikary, tak, możesz mieć funkcję async w useEffect, wystarczy napisać ją inaczej. Proszę sprawdzić stackoverflow.com/questions/53332321/…
Shubham Khatri,
75
./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

To nie jest błąd JS / React, ale ostrzeżenie eslint (eslint-plugin-aware-hooks).

Mówi ci, że hook zależy od funkcji fetchBusinesses, więc powinieneś przekazać to jako zależność.

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

Mogłoby to spowodować wywołanie funkcji przy każdym renderowaniu, jeśli funkcja jest zadeklarowana w komponencie, na przykład:

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

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

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

  /*...*/
}

ponieważ za każdym razem funkcja jest ponownie deklarowana z nowym odniesieniem

Prawidłowy sposób na zrobienie tego to:

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

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

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

  /*...*/
}

lub po prostu definiując funkcję w useEffect

Więcej: https://github.com/facebook/react/issues/14920

fard
źródło
14
Powoduje to nowy błądLine 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
rozwiązanie jest w porządku i jeśli w funkcji modyfikujesz inny stan, musisz dodać zależności, aby uniknąć kolejnego nieoczekiwanego zachowania
cesarlarsson
55

Możesz ustawić go bezpośrednio jako useEffectcallback:

useEffect(fetchBusinesses, [])

Uruchomi się tylko raz, więc upewnij się, że wszystkie zależności funkcji są poprawnie ustawione (tak samo jak użycie componentDidMount/componentWillMount...)


Edycja 21.02.2020

Tylko dla kompletności:

1. Użyj funkcji jako useEffectwywołania zwrotnego (jak wyżej)

useEffect(fetchBusinesses, [])

2. Zadeklaruj funkcję wewnątrz useEffect()

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

3. Zapamiętaj za pomocą useCallback()

W takim przypadku, jeśli masz zależności w swojej funkcji, będziesz musiał uwzględnić je w useCallbacktablicy zależności, a to uruchomi useEffectponownie, jeśli parametry funkcji ulegną zmianie. Poza tym jest dużo schematu ... Więc po prostu przekaż funkcję bezpośrednio do useEffectas in 1. useEffect(fetchBusinesses, []).

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

4. Wyłącz ostrzeżenie eslint

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
jpenna
źródło
2
Kocham cię ... Ta odpowiedź jest kompletna!
Nick 09
8

Rozwiązaniem jest również reakcja, radzą ci użyć, useCallbackktóra zwróci wersję memoize twojej funkcji:

Funkcja „fetchBusinesses” powoduje, że zależności hooka useEffect (w linii NN) zmieniają się przy każdym renderowaniu. Aby to naprawić, zawiń definicję „fetchBusinesses” do jej własnego użytku. Callback () Hook reaguje-hooks / wyczerpująco-deps

useCallbackjest prosty w użyciu, ponieważ ma taką samą sygnaturę, jak useEffectróżnica polega na tym, że useCallback zwraca funkcję. Wyglądałoby to tak:

 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]);
Stephane L.
źródło
1

Ten artykuł jest dobrym podkładem do pobierania danych za pomocą hooków: https://www.robinwieruch.de/react-hooks-fetch-data/

Zasadniczo dołącz definicję funkcji pobierania do środka useEffect:

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

  fetchBusinesses();
}, []);
helloitsjoe
źródło
1

Możesz usunąć tablicę typu drugiego argumentu, []ale fetchBusinesses()będzie ona również nazywana każdą aktualizacją. Jeśli chcesz, możesz dodać IFoświadczenie do fetchBusinesses()implementacji.

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

Drugim jest zaimplementowanie fetchBusinesses()funkcji poza komponentem. Po prostu nie zapomnij przekazać wszelkich argumentów zależności do swojego fetchBusinesses(dependency)wywołania, jeśli takie istnieją.

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

  // ...
}
5ervant
źródło
0

W rzeczywistości ostrzeżenia są bardzo przydatne, gdy rozwijasz się z hakami. ale w niektórych przypadkach może cię zranić. zwłaszcza gdy nie musisz nasłuchiwać zmian zależności.

Jeśli nie chcesz wstawiać fetchBusinesseswewnątrz zależności podpięcia, możesz po prostu przekazać to jako argument do wywołania zwrotnego hooka i ustawić main fetchBusinessesjako wartość domyślną w ten sposób

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

Nie jest to najlepsza praktyka, ale w niektórych przypadkach może być przydatna.

Jak napisał Shubnam, możesz dodać poniższy kod, aby powiedzieć ESLint, aby zignorował sprawdzanie twojego hooka.

// eslint-disable-next-line react-hooks/exhaustive-deps
Behnam Azimi
źródło
0

Chcę tylko uruchomić [ fetchBusinesses] raz na początku, podobnie jakcomponentDidMount()

Możesz fetchBusinessescałkowicie wyciągnąć ze swojego komponentu:

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>;
};

Zapewni to nie tylko proste rozwiązanie i rozwiąże ostrzeżenie wyczerpujące. fetchBusinessteraz można go lepiej przetestować i łatwiejComp , ponieważ znajduje się w zakresie modułu poza drzewem React.

Przeniesienie na fetchBusinesseszewnątrz działa tutaj dobrze, ponieważ i tak bylibyśmy w stanie odczytać tylko początkowe właściwości i stan z komponentu z powodu nieaktualnego zakresu zamknięcia ( []dep in useEffect).

Jak pominąć zależności funkcji

  • Przenieś funkcję wewnątrz efektu
  • Przenieś funkcję poza komponent - (używamy tego)
  • Wywołaj funkcję podczas renderowania i pozwól useEffect zależeć od tej wartości (czysta funkcja obliczeniowa)
  • Dodaj funkcję do efektu deps i zawiń ją useCallbackw ostateczności

W przypadku innych rozwiązań:

Wciągnięcie do fetchBusinessesśrodka useEffect()nie pomaga, jeśli uzyskasz dostęp do innego stanu w nim. eslint nadal narzekałby: Codesandbox .

Chciałbym też powstrzymać się od eslint wyczerpującego-deps ignorować komentarze. Po prostu łatwo o nich zapomnieć, dokonując refaktoryzacji i przeglądu swoich zależności.

ford04
źródło
0
const [mount, setMount] = useState(false)
const fetchBusinesses = () => { 
   //function defination
}
useEffect(() => {
   if(!mount) {
      setMount(true);
      fetchBusinesses();
   }
},[fetchBusinesses]);

To rozwiązanie jest dość proste i nie ma potrzeby zastępowania ostrzeżeń es-lint. Po prostu utrzymuj flagę, aby sprawdzić, czy komponent jest zamontowany, czy nie.

Yasin
źródło
0

spróbuj w ten sposób

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

i

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

to dla ciebie praca. Ale moja sugestia jest taka, aby spróbować w ten sposób również dla Ciebie. Jest lepiej niż wcześniej. Używam w ten sposób:

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();
      }, []);

jeśli otrzymujesz dane na podstawie określonego identyfikatora, dodaj wywołanie zwrotne useEffect, [id]a następnie nie możesz pokazać ostrzeżenia React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array

Kashif
źródło
-4

po prostu wyłącz eslint dla następnej linii;

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

w ten sposób używasz go tak, jak komponent został zamontowany (wywołany raz)

zaktualizowany

lub

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

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

fetchBusinesses będzie wywoływane za każdym razem, gdy SomeDeps ulegnie zmianie

user3550446
źródło
zamiast wyłączać, po prostu [fetchBusinesses]zrobię to: automatycznie usunie ostrzeżenie i to rozwiązało problem.
rotimi-best
7
@RotimiBest - zrobienie tego powoduje nieskończone ponowne renderowanie, jak opisano w pytaniu
user210757
Właściwie zrobiłem to w ten sposób jakiś czas temu w jednym z moich projektów i nie stworzyło to nieskończonej pętli. Jeszcze raz sprawdzę.
rotimi-best
@ user210757 Poczekaj, ale dlaczego spowoduje to nieskończoną pętlę, to nie tak, jakbyś ustawiał stan po pobraniu danych z serwera. Jeśli aktualizowałeś stan, po prostu napisz warunek if przed wywołaniem funkcji, useEffectktóra sprawdza, czy stan jest pusty.
rotimi-best
@ rotimi-best było ahwile odkąd skomentowałem, ale powiedziałbym, że funkcja jest ponownie tworzona za każdym razem, więc nigdy nie jest taka sama, więc zawsze będzie renderowana ponownie, chyba że przejdziesz do treści useEffect lub useCallback
user210757