Como corrigir o aviso de dependência ausente ao usar o useEffect React Hook?

178

Com o React 16.8.6 (foi bom na versão anterior 16.8.3), recebo esse erro ao tentar impedir um loop infinito em uma solicitação de busca

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

Não consegui encontrar uma solução que interrompa o loop infinito. Eu quero ficar longe de usar useReducer(). Eu encontrei essa discussão em https://github.com/facebook/react/issues/14920, onde uma possível solução é You can always // eslint-disable-next-line react-hooks/exhaustive-deps if you think you know what you're doing.não estar confiante no que estou fazendo, por isso ainda não tentei implementá-la.

Eu tenho essa configuração atual React hook useEffect é executado continuamente para sempre / loop infinito e o único comentário é sobre o useCallback()qual eu não estou familiarizado.

Como estou usando no momento useEffect()(que só quero executar uma vez no começo, semelhante 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
fonte

Respostas:

191

Se você não estiver usando o método fetchBusinesses em lugar algum do efeito, basta movê-lo para o efeito e evitar o aviso

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, no entanto, você estiver usando fetchBusinesses fora da renderização, observe duas coisas

  1. Existe algum problema em você não passar fetchBusinessescomo método quando é usado durante a montagem com seu fechamento?
  2. Seu método depende de algumas variáveis ​​que recebe de seu fechamento anexo? Este não é o seu caso.
  3. Em cada renderização, o fetchBusinesses será recriado e, portanto, passá-lo para useEffect causará problemas. Então, primeiro você deve memorizar o fetchBusinesses se quiser passá-lo para a matriz de dependências.

Para resumir, eu diria que se você estiver usando fetchBusinessesfora de useEffectvocê, poderá desativar a regra usando, // eslint-disable-next-line react-hooks/exhaustive-depscaso contrário, poderá mover o método para dentro de useEffect

Para desativar a regra, escreva-a como

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

   // eslint-disable-next-line react-hooks/exhaustive-deps
}, []) 
Shubham Khatri
fonte
14
Eu usei a solução que você descreveu bem. Outra solução que eu usei em outro lugar por causa de uma configuração diferente foi useCallback(). Assim, por exemplo: const fetchBusinesses= useCallback(() => { ... }, [...]) e useEffect()ficaria assim:useEffect(() => { fetchBusinesses(); }, [fetchBusinesses]);
russ
1
@russ, você está correto, você precisa memoize fetchBusiness usando useCallback se você está a passá-lo para variedade dependência
Shubham Khatri
Seria bom se você mostrasse onde colocar a instrução eslint-disable. Eu acho que seria acima de useEffect
user210757
1
usar // eslint-disable-next-line react-hooks/exhaustive-depspara explicar ao linter que seu código está correto é como um hack. Espero que eles vão encontrar uma outra solução para tornar o linter mais inteligente o suficiente para detectar quando um argumento não é obrigatório
Olivier Boisse
1
@TapasAdhikary, sim, você pode ter uma função assíncrona em useEffect, basta escrever de forma diferente. Verifique 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

Não é erro de JS / React, mas aviso de eslint (eslint-plugin-react-hooks).

Ele está lhe dizendo que o gancho depende da função fetchBusinesses, então você deve passá-lo como dependência.

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

Isso poderia resultar na chamada da função a cada renderização se a função for declarada no componente como:

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

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

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

  /*...*/
}

porque toda vez que a função é redeclarada com nova referência

A maneira correta de fazer isso é:

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

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

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

  /*...*/
}

ou apenas definindo função em useEffect

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

fardar
fonte
14
Isso resulta em um novo erroLine 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
a solução está bem e se a função modifica outro estado você tem que adicionar os dependecies para evitar outro inesperado comportamento
cesarlarsson
57

Você pode configurá-lo diretamente como useEffectretorno de chamada:

useEffect(fetchBusinesses, [])

Ele será acionado apenas uma vez, portanto, verifique se todas as dependências da função estão definidas corretamente (o mesmo que usar componentDidMount/componentWillMount... )


Editar 21/02/2020

Apenas para completar:

1. Use a função como useEffectretorno de chamada (como acima)

useEffect(fetchBusinesses, [])

2. Declare a função para dentro useEffect()

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

3. Memorize com useCallback()

Nesse caso, se você tiver dependências em sua função, precisará incluí-las na useCallbackmatriz de dependências e isso será acionado useEffectnovamente se os parâmetros da função mudarem. Além disso, é um monte de clichê ... Então, basta passar a função diretamente para useEffectcomo em 1. useEffect(fetchBusinesses, []).

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

4. Desative o aviso de eslint

useEffect(() => {
  fetchBusinesses()
}, []) // eslint-disable-line react-hooks/exhaustive-deps
jpenna
fonte
2
Eu te amo ... Esta resposta é tão completa!
Nick09 24/03
8

A solução também é dada pelo useCallbackreact , eles recomendam que você retorne uma versão memoize da sua função:

A função 'fetchBusinesses' faz com que as dependências do useEffect Hook (na linha NN) sejam alteradas a cada renderização. Para corrigir isso, envolva a definição 'fetchBusinesses' em seu próprio uso: Callback () Hook react-hooks / complete-deps

useCallbacké simples de usar, pois tem a mesma assinatura que useEffecta diferença é que useCallback retorna uma função. Seria assim:

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

Este artigo é um bom manual para obter dados com ganchos: https://www.robinwieruch.de/react-hooks-fetch-data/

Essencialmente, inclua a definição da função de busca dentro useEffect:

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

  fetchBusinesses();
}, []);
helloitsjoe
fonte
1

Você pode remover a segunda matriz do tipo de argumento, []mas fetchBusinesses()ela também será chamada a cada atualização. Você pode adicionar uma IFdeclaração à fetchBusinesses()implementação, se quiser.

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

O outro é implementar a fetchBusinesses()função fora do seu componente. Apenas não esqueça de passar argumentos de dependência para sua fetchBusinesses(dependency)ligação, se houver.

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
fonte
0

Na verdade, os avisos são muito úteis quando você desenvolve com ganchos. mas, em alguns casos, pode agulhá-lo. especialmente quando você não precisa escutar alterações nas dependências.

Se você não deseja colocar fetchBusinessesdentro das dependências do gancho, basta passá-lo como argumento para o retorno de chamada do gancho e definir o principal fetchBusinessescomo o valor padrão para este tipo de ação.

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

Não é uma prática recomendada, mas pode ser útil em alguns casos.

Também como Shubnam escreveu, você pode adicionar o código abaixo para dizer ao ESLint para ignorar a verificação do seu gancho.

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

Eu só quero rodar [ fetchBusinesses] uma vez no começo semelhante acomponentDidMount()

Você pode retirar fetchBusinessescompletamente do seu 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>;
};

Isso não apenas fornecerá uma solução simples e resolverá o aviso de esgotamentos. fetchBusinessagora pode ser testado melhor e facilitaComp , pois reside no escopo do módulo fora da árvore do React.

A realocação fetchBusinessesexterna funciona bem aqui, pois só poderíamos ler adereços e estados iniciais do componente de qualquer maneira devido ao escopo de fechamento obsoleto ( []dep in useEffect).

Como omitir dependências de função

  • Mover a função para dentro do efeito
  • Mover a função para fora do componente - (estamos usando este)
  • Chame a função durante a renderização e deixe useEffectdepender desse valor (função de computação pura)
  • Adicione a função para efetuar deps e envolva-a com useCallback como último recurso

Em relação a outras soluções:

Puxando para fetchBusinessesdentrouseEffect() realmente não ajuda, se você acessar outro estado nele. O eslint ainda reclamaria: Codesandbox .

Eu também evitaria os deps exaustivos que ignoram os comentários. É fácil esquecê-los quando você faz alguma refatoração e revisão de suas dependências.

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

Esta é uma solução bastante simples e você não precisa substituir os avisos es-lint. Basta manter um sinalizador para verificar se o componente está montado ou não.

Yasin
fonte
0

você tenta assim

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

é trabalho para você. Mas a minha sugestão é tentar desta forma também trabalhar para você. É melhor do que antes. Eu uso desta maneira:

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 você obtiver dados com base na identificação específica, adicione o uso de retorno de chamada useEffect [id], não será possível exibir aviso React Hook useEffect has a missing dependency: 'any thing'. Either include it or remove the dependency array

Kashif
fonte
-4

basta desativar o eslint para a próxima linha;

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

dessa maneira, você o está usando exatamente como um componente foi montado (chamado uma vez)

Atualizada

ou

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

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

fetchBusinesses será chamado sempre que someDeps mudar

user3550446
fonte
em vez de desativar, basta fazer o seguinte: [fetchBusinesses]removerá automaticamente o aviso e isso resolveu o problema para mim.
rotimi-best
7
@RotimiBest - fazendo isso provoca uma infinita re-render como descrito na pergunta
user210757
Na verdade, eu fiz dessa maneira em um dos meus projetos há um tempo atrás e não produziu um loop infinito. Vou verificar novamente embora.
rotimi-best 19/06/19
@ user210757 Aguarde, mas por que isso causará um loop infinito? Não é como se você estivesse definindo o estado após buscar os dados no servidor. Se você estava atualizando o estado, basta escrever uma condição if antes de chamar a função para useEffectverificar se o estado está vazio.
rotimi-best
@ rotimi-best hawile desde que comentei, mas eu diria que a função é recriada todas as vezes, portanto nunca é a mesma, portanto sempre será renderizada novamente, a menos que você mude para o corpo useEffect ou useCallback
user210757