¿Cómo reparar la advertencia de dependencia faltante cuando se usa useEffect React Hook?

178

Con React 16.8.6 (fue bueno en la versión anterior 16.8.3), obtengo este error cuando intento evitar un bucle infinito en una solicitud de recuperación

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

No he podido encontrar una solución que detenga el ciclo infinito. Quiero alejarme del uso useReducer(). Encontré esta discusión https://github.com/facebook/react/issues/14920 donde una posible solución es You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.que no estoy seguro de lo que estoy haciendo, así que todavía no he intentado implementarlo.

Tengo esta configuración actual React hook useEffect se ejecuta continuamente para siempre / bucle infinito y el único comentario es sobre el useCallback()que no estoy familiarizado.

Cómo estoy usando actualmente useEffect()(que solo quiero ejecutar una vez al principio, similar 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
      });
  };
russ
fuente

Respuestas:

191

Si no está utilizando el método fetchBusinesses aparte del efecto, simplemente puede moverlo al efecto y evitar la advertencia

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

Sin embargo, si está utilizando fetchBusinesses fuera del render, debe tener en cuenta dos cosas

  1. ¿Hay algún problema con que no pases fetchBusinessescomo método cuando se usa durante el montaje con su cierre envolvente?
  2. ¿Su método depende de algunas variables que recibe de su cierre de cierre? Este no es el caso para ti.
  3. En cada render, fetchBusinesses se volverá a crear y, por lo tanto, pasarlo a useEffect causará problemas. Entonces, primero debe memorizar fetchBusinesses si fuera a pasarlo a la matriz de dependencias.

Para resumir, diría que si está usando fetchBusinessesfuera de useEffectusted puede deshabilitar la regla usando de lo // eslint-disable-next-line react-hooks/exhaustive-depscontrario puede mover el método dentro de useEffect

Para deshabilitar la regla, la escribirías como

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

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Shubham Khatri
fuente
14
Usé la solución que describiste muy bien. Otra solución que usé en otro lugar fue debido a una configuración diferente useCallback(). Entonces, por ejemplo: const fetchBusinesses= useCallback(() => { ... }, [...]) y useEffect()se vería así:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
russ
1
@russ, estás en lo correcto, necesitarías memorizar fetchBusiness usando useCallback si vas a pasarlo a una matriz de dependencias
Shubham Khatri
Sería bueno que mostraras dónde colocar la instrucción eslint-disable. Pensé que estaría por encima de useEffect
user210757
1
usar // eslint-disable-next-line react-hooks/exhaustive-depspara explicarle a la interfaz que tu código es correcto es como un hack. Espero que encuentren otra solución para hacer que el linter sea lo suficientemente inteligente como para detectar cuando un argumento no es obligatorio
Olivier Boissé
1
@TapasAdhikary, sí, puede tener una función asíncrona en useEffect, solo necesita escribirla de manera diferente. Verifique stackoverflow.com/questions/53332321/…
Shubham Khatri el
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

No es un error JS / React sino una advertencia de eslint (eslint-plugin-react-hooks).

Le dice que el gancho depende de la función fetchBusinesses, por lo que debe pasarlo como dependencia.

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

Podría provocar la invocación de la función cada render si la función se declara en un componente como:

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

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

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

  /*...*/
}

porque cada vez que la función se redeclara con nueva referencia

La forma correcta de hacer esto es:

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

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

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

  /*...*/
}

o simplemente definiendo la función en useEffect

Más: https://github.com/facebook/react/issues/14920

fard
fuente
14
Esto resulta en un nuevo errorLine 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 solución está bien y si en la función modifica otro estado, debe agregar las dependencias para evitar otro comportamiento inesperado
cesarlarsson
57

Puede configurarlo directamente como useEffectdevolución de llamada:

useEffect(fetchBusinesses, [])

Se activará solo una vez, así que asegúrese de que todas las dependencias de la función estén configuradas correctamente (lo mismo que usar componentDidMount/componentWillMount...)


Editar 21/02/2020

Solo para completar:

1. Use la función como useEffectdevolución de llamada (como arriba)

useEffect(fetchBusinesses, [])

2. Declarar función dentro useEffect()

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

3. Recuerda con useCallback()

En este caso, si tiene dependencias en su función, deberá incluirlas en la useCallbackmatriz de dependencias y esto activará useEffectnuevamente si los parámetros de la función cambian. Además, es un montón de repeticiones ... Así que simplemente pase la función directamente a useEffectcomo en 1. useEffect(fetchBusinesses, []).

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

4. Desactiva la advertencia de eslint

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
jpenna
fuente
2
Te amo ... ¡Esta respuesta es tan completa!
Nick09
8

La solución también está dada por react, los consejos que utiliza useCallbackque le devolverán una versión memorable de su función:

La función 'fetchBusinesses' hace que las dependencias de useEffect Hook (en la línea NN) cambien en cada render. Para solucionar esto, envuelva la definición 'fetchBusinesses' en su propio usoCallback () Hook react-hooks / exhaustive-deps

useCallbackes simple de usar ya que tiene la misma firma ya que useEffectla diferencia es que useCallback devuelve una función. Se vería así:

 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
fuente
1

Este artículo es una buena introducción a la obtención de datos con ganchos: https://www.robinwieruch.de/react-hooks-fetch-data/

Esencialmente, incluya la definición de la función de búsqueda dentro useEffect:

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

  fetchBusinesses();
}, []);
holaitsjoe
fuente
1

Puede eliminar el segundo tipo de matriz de argumentos, []pero fetchBusinesses()también se llamará a cada actualización. Puede agregar una IFdeclaración a la fetchBusinesses()implementación si lo desea.

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

El otro es implementar la fetchBusinesses()función fuera de su componente. Simplemente no olvide pasar ningún argumento de dependencia a su fetchBusinesses(dependency)llamada, si corresponde.

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

  // ...
}
5viente
fuente
0

En realidad, las advertencias son muy útiles cuando se desarrolla con ganchos. pero en algunos casos, puede hacerte una aguja. especialmente cuando no necesita escuchar el cambio de dependencias.

Si no desea colocar fetchBusinessesdentro de las dependencias del gancho, simplemente puede pasarlo como un argumento a la devolución de llamada del gancho y establecer el principal fetchBusinessescomo el valor predeterminado para ello de esta manera

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

No es la mejor práctica, pero podría ser útil en algunos casos.

Además, como escribió Shubnam, puede agregar el siguiente código para indicarle a ESLint que ignore la comprobación de su enlace.

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

Solo quiero ejecutar [ fetchBusinesses] una vez al principio similar acomponentDidMount()

Puede sacar fetchBusinessescompletamente de su 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>;
};

Esto no solo proporcionará una solución simple y resolverá la advertencia exhaustiva de profundidad. fetchBusinessahora puede probarse mejor y facilitarse Comp, ya que reside en el alcance del módulo fuera del árbol React.

Reubicar fetchBusinessesafuera funciona bien aquí, ya que solo podríamos leer los accesorios iniciales y el estado del componente de todos modos debido al alcance de cierre obsoleto ( []dep in useEffect).

Cómo omitir dependencias de funciones

  • Mueve la función dentro del efecto
  • Mueva la función fuera del componente - (estamos usando este)
  • Llama a la función durante el renderizado y deja que useEffectdependa de este valor (función de cálculo puro)
  • Agregue la función para efectuar departamentos y envolverlo useCallbackcomo último recurso

En cuanto a otras soluciones:

Entrar fetchBusinessesadentro useEffect()realmente no ayuda, si accede a otro estado en él. Eslint todavía se quejaría: Codesandbox .

También me mantendría alejado de eslint exhaustivo-deps ignorar los comentarios. Es fácil olvidarlos cuando refactoriza y revisa sus dependencias.

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

Esta solución es bastante simple y no necesita anular las advertencias de es-lint. Simplemente mantenga una bandera para verificar si el componente está montado o no.

Yasin
fuente
0

intentas de esta manera

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

y

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

Es trabajo para ti. Pero mi sugerencia es que intente de esta manera también le funcione Es mejor que antes. Yo uso de esta manera:

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 obtiene datos sobre la base de una identificación específica, agregue el efecto de devolución de llamada useEffect [id]y no pueda mostrarle la advertencia React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array

Kashif
fuente
-4

simplemente deshabilite eslint para la siguiente línea;

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

de esta manera lo está utilizando como si un componente se hubiera montado (llamado una vez)

actualizado

o

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

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

fetchBusinesses se llamará cada vez que someDeps cambie

usuario3550446
fuente
en lugar de deshabilitar, solo haciendo esto: [fetchBusinesses]eliminará automáticamente la advertencia y eso resolvió el problema para mí.
rotimi-best
77
@RotimiBest - hacer esto causa una infinita volver a hacer como se describe en la pregunta
user210757
De hecho, lo hice de esta manera en uno de mis proyectos hace un tiempo y no produjo un bucle infinito. Sin embargo, volveré a verificar.
rotimi-best
@ user210757 Espere, pero ¿por qué provocará un bucle infinito? No es como si estuviera configurando el estado después de recuperar los datos del servidor. Si estaba actualizando el estado, simplemente escriba una condición if antes de llamar a la función useEffectque verifica si el estado está vacío.
Rotimi-mejor
@ Rotimi-best ha estado desde que comenté, pero diría que la función se vuelve a crear cada vez, por lo tanto, nunca será la misma, por lo que siempre se volverá a representar, a menos que pase al cuerpo useEffect o useCallback
user210757