Come correggere l'avviso di dipendenza mancante quando si utilizza useEffect React Hook?


178

Con React 16.8.6 (era buono sulla versione precedente 16.8.3), ottengo questo errore quando provo a prevenire un ciclo infinito su una richiesta di recupero

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

Non sono stato in grado di trovare una soluzione che arresti il ​​ciclo infinito. Voglio stare lontano dall'uso useReducer(). Ho trovato questa discussione https://github.com/facebook/react/issues/14920 in cui una possibile soluzione è You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.che non sono fiducioso in quello che sto facendo, quindi non ho ancora provato a implementarlo.

Ho questa configurazione corrente React hook use L'effetto funziona in modo continuo per sempre / ciclo infinito e l'unico commento riguarda il useCallback()quale non ho familiarità.

Come sto attualmente utilizzando useEffect()(che voglio eseguire solo una volta all'inizio simile a 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
      });
  };

Risposte:


191

Se non stai usando il metodo fetchBusinesses in nessun luogo diverso dall'effetto, puoi semplicemente spostarlo nell'effetto ed evitare l'avvertimento

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

Se tuttavia stai usando fetchBusinesses al di fuori del rendering, devi notare due cose

  1. C'è qualche problema con te che non passi fetchBusinessescome metodo quando viene usato durante il montaggio con la sua chiusura chiusa?
  2. Il tuo metodo dipende da alcune variabili che riceve dalla sua chiusura? Questo non è il tuo caso.
  3. Su ogni rendering, fetchBusinesses verrà ricreato e quindi passarlo a useEffect causerà problemi. Quindi, prima di tutto devi memorizzare in fetchBusinesses se lo passassi all'array di dipendenze.

Per riassumere, direi che se stai usando fetchBusinessesal di fuori di useEffectte puoi disabilitare la regola usando // eslint-disable-next-line react-hooks/exhaustive-depsaltrimenti puoi spostare il metodo all'interno di useEffect

Per disabilitare la regola dovresti scriverla come

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

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 

14
Ho usato bene la soluzione che hai delineato. Un'altra soluzione che ho usato altrove a causa di una configurazione diversa era useCallback(). Quindi per esempio: const fetchBusinesses= useCallback(() => { ... }, [...]) e useEffect()sarebbe simile al seguente:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
russ

1
@russ, hai ragione, dovresti memoize fetchBusiness usando useCallback se devi passarlo all'array di dipendenze
Shubham Khatri

Sarebbe bello se mostrassi dove mettere l'istruzione eslint-disable. Ho pensato che sarebbe stato al di sopra di useEffect
user210757

1
usare // eslint-disable-next-line react-hooks/exhaustive-depsper spiegare alla linter che il tuo codice è corretto è come un hack. Mi aspetto che troveranno un'altra soluzione per rendere la linter abbastanza più intelligente da rilevare quando una discussione non è obbligatoria
Olivier Boissé,

1
@TapasAdhikary, sì, puoi utilizzare una funzione asincrona in useEffect, devi solo scriverla in modo diverso. Si prega di verificare 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

Non è un errore JS / React ma avvertimento eslint (eslint-plugin-reazioni-hooks).

Ti sta dicendo che l'hook dipende dalla funzione fetchBusinesses, quindi dovresti passarlo come dipendenza.

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

Potrebbe comportare il richiamo della funzione ogni rendering se la funzione è dichiarata nel componente come:

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

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

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

  /*...*/
}

perché ogni volta che la funzione viene redeclared con nuovo riferimento

Il modo corretto di fare queste cose è:

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

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

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

  /*...*/
}

o semplicemente definendo la funzione in useEffect

Altro: https://github.com/facebook/react/issues/14920


14
Ciò si traduce in un nuovo erroreLine 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 soluzione va bene e se sulla funzione si modifica un altro stato è necessario aggiungere le dipendenze per evitare un altro comportamento imprevisto
cesarlarsson

57

Puoi impostarlo direttamente come useEffectcallback:

useEffect(fetchBusinesses, [])

Si attiverà una sola volta, quindi assicurati che tutte le dipendenze della funzione siano impostate correttamente (come nel caso dell'uso componentDidMount/componentWillMount...)


Modifica 21/02/2020

Solo per completezza:

1. Usa la funzione come useEffectcallback (come sopra)

useEffect(fetchBusinesses, [])

2. Dichiarare la funzione all'interno useEffect()

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

3. Memoize con useCallback()

In questo caso, se hai dipendenze nella tua funzione, dovrai includerle useCallbacknell'array delle dipendenze e questo attiverà di useEffectnuovo se i parametri della funzione cambiano. Inoltre, è un sacco di boilerplate ... Quindi basta passare la funzione direttamente a useEffectcome in 1. useEffect(fetchBusinesses, []).

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

4. Disabilita l'avviso di eslint

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps

2
Ti amo ... Questa risposta è così completa!
Nick09

8

La soluzione è anche data da reagire, ti consigliano di usare useCallbackche restituirà una versione memoize della tua funzione:

La funzione 'fetchBusinesses' cambia le dipendenze di useEffect Hook (alla linea NN) su ogni rendering. Per risolvere questo problema, avvolgi la definizione di 'fetchBusinesses' nel suo uso. Callback () Hook reazioni-ganci / esaustivi

useCallbackè semplice da usare in quanto ha la stessa firma useEffectdella differenza è che useCallback restituisce una funzione. Sarebbe così:

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


1

Questo articolo è un buon primer sul recupero dei dati con hook: https://www.robinwieruch.de/react-hooks-fetch-data/

In sostanza, includi la definizione della funzione fetch all'interno useEffect:

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

  fetchBusinesses();
}, []);

1

È possibile rimuovere l'array di tipo 2 ° argomento []ma fetchBusinesses()verrà anche chiamato ogni aggiornamento. Se lo desideri, puoi aggiungere una IFdichiarazione fetchBusinesses()nell'implementazione.

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

L'altro è implementare la fetchBusinesses()funzione all'esterno del componente. Basta non dimenticare di passare eventuali argomenti di dipendenza alla fetchBusinesses(dependency)chiamata, se presenti.

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

  // ...
}

0

In realtà gli avvisi sono molto utili quando si sviluppa con ganci. ma in alcuni casi, può agitarti. soprattutto quando non è necessario ascoltare il cambiamento delle dipendenze.

Se non vuoi mettere fetchBusinessesdentro le dipendenze dell'hook, puoi semplicemente passarlo come argomento al callback dell'hook e impostare main fetchBusinessescome valore predefinito per questo in questo modo

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

Non è la migliore pratica ma potrebbe essere utile in alcuni casi.

Inoltre, come ha scritto Shubnam, puoi aggiungere il codice seguente per dire a ESLint di ignorare il controllo del tuo hook.

// eslint-disable-next-line react-hooks/exhaustive-deps

0

Voglio eseguire [ fetchBusinesses] solo una volta all'inizio simile acomponentDidMount()

Puoi estrarre fetchBusinessescompletamente il tuo componente:

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

Ciò non solo fornirà una soluzione semplice e risolverà l'avvertimento approfondito dei deps. fetchBusinessora può essere testato meglio e facilitato Comp, poiché risiede nell'ambito del modulo all'esterno dell'albero React.

Il trasferimento fetchBusinessesall'esterno funziona bene qui, dato che saremmo in grado di leggere gli oggetti di scena iniziali e di dichiararli comunque dal componente a causa dell'ambito di chiusura obsoleto ( []dep in useEffect).

Come omettere le dipendenze delle funzioni

  • Sposta la funzione all'interno dell'effetto
  • Sposta la funzione fuori dal componente - (stiamo usando questo)
  • Richiama la funzione durante il rendering e lascia che useEffectdipenda da questo valore (pura funzione di calcolo)
  • Aggiungi la funzione per effettuare deps e avvolgila useCallbackcome ultima risorsa

Per quanto riguarda le altre soluzioni:

Tirare fetchBusinessesdentro useEffect()non aiuta davvero, se si accede ad altri stati in esso. eslint si lamenterebbe ancora: Codesandbox .

Vorrei anche trattenermi da un esauriente approfondimento, ignorare i commenti. È facile dimenticarli quando esegui un refactoring e una revisione delle tue dipendenze.


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

Questa soluzione è piuttosto semplice e non è necessario ignorare gli avvisi es-lint. Basta mantenere un flag per verificare se il componente è montato o meno.


0

ci provi così

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

e

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

è lavoro per te. Ma il mio suggerimento è provare in questo modo anche per te. È meglio di prima. Uso in questo modo:

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

se si ottengono dati sulla base di un ID specifico, quindi si aggiunge in callback useEffect, [id]quindi non è possibile visualizzare un avviso React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array


-4

basta disabilitare eslint per la riga successiva;

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

in questo modo lo stai usando proprio come un componente ha montato (chiamato una volta)

aggiornato

o

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

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

fetchBusinesses verrà chiamato ogni volta che cambierà qualche Dipartimento


invece di disabilitare, basta fare questo: [fetchBusinesses]rimuoverà automaticamente l'avviso e questo ha risolto il problema per me.
Rotimi-best

7
@RotimiBest - farlo causa un re-rendering infinito come descritto nella domanda
user210757

L'ho fatto in questo modo in uno dei miei progetti qualche tempo fa e non ha prodotto un ciclo infinito. Controllerò di nuovo però.
Rotimi-best

@ user210757 Aspetta, ma perché causerà un ciclo infinito, non è come se stessi impostando lo stato dopo aver recuperato i dati dal server. Se stavi aggiornando lo stato, scrivi semplicemente una condizione if prima di chiamare la funzione useEffectche controlla se lo stato è vuoto.
Rotimi-best

@ rotimi-best è stato meglio da quando ho commentato, ma direi che la funzione viene ricreata ogni volta, quindi mai lo stesso, quindi sarà sempre ri-renderizzata, a meno che non ti sposti nel corpo useEffect o useCallback
user210757
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.