DEV Community

Cover image for Cancelar requisições Fetch em React useEffect
Eduardo Rabelo
Eduardo Rabelo

Posted on

2 1

Cancelar requisições Fetch em React useEffect

O useEffect é um poderoso hook para realizar efeitos em suas aplicações React usando a sintaxe de componentes em funções.

Ao retornar uma função dentro do useEffect estamos entrando na faze de limpeza do efeito.

Como mostra a documentação, em componentes de classe, usaríamos os ciclos de vida componentDidMount e componentWillUnmount:

class FriendStatus extends React.Component {
  constructor(props) { ... }

  componentDidMount() { // [ A ]
    ChatAPI.subscribeToFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  componentWillUnmount() { // [ B ]
    ChatAPI.unsubscribeFromFriendStatus(
      this.props.friend.id,
      this.handleStatusChange
    );
  }

  handleStatusChange(status) { ... }

  render() { ... }
}
Enter fullscreen mode Exit fullscreen mode

O exemplo acima pode ser resumido em:

  • [ A ]: Ao montar o componente, criamos uma inscrição/escuta na API ChatAPI.subscribeToFriendStatus e iremos executar a função handleStatusChange para cada mudança
  • [ B ]: Quando o componente for removido, estamos retirando essa inscrição/escuta, para evitar problemas, como vazamento de memória (memory-leaks)

Assim como mostrado na documentação, usando useEffect, teríamos a seguinte sintaxe:

function FriendStatus(props) {
  ...
  useEffect(() => {
    function handleStatusChange(status) { ... }

    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);

    return function cleanup() { // [ C ]
      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);
    };
  });
  ...
}
Enter fullscreen mode Exit fullscreen mode

Perceba que estamos retornando uma função em [ C ], ela será executada pelo React ao remover o componente, removendo corretamente (a declração de função function cleanup() {} é opcional, você pode retornar uma função de seta () => {}, para didática do exemplo, estou copiando a documentação do React).

Com esse conceito fresco em mente, vamos falar da Fetch API.

Fetch API

A interface retornada pela Fetch API nos permite utilizar o Abort API, onde podemos passar um controlador para a requisição e, se necessário, realizar o cancelamento da requisição.

Traduzindo isso para código, teríamos a seguinte sintaxe:

const controller = new AbortController();
const signal = controller.signal();

fetch("minha-url", { ...headers, signal }); // [ D ]

// ... um futuro qualquer
// cancela/aborta [ D ] se ainda estiver em execução
controller.abort()
Enter fullscreen mode Exit fullscreen mode

Não vamos discutir os detalhes do significado "requisição em execução", porém, um ponto que vale a pena comentar é: tome cuidado ao cancelar/abortar requisições que não são GET, por exemplo, POST/PUT/DELETE.

Agora que sabemos como transformar nossa requisição Fetch, podemos ter o seguinte fluxo:

  • Dentro de um useEffect, criamos um AbortController
  • Passamos para nosso fetch o signal
  • Retornamos uma função de limpeza no useEffect e executamos o .abort() dentro dela

Teríamos a seguinte sintaxe:

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal();

  fetch("minha-url", { signal });

  return () => {
    controller.abort();
  }
})
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, estamos cancelando nossa requisição toda vez que o efeito for executado.

Que tal um exemplo prático?

Colocando tudo junto

Utilizando a TheCatApi como serviço, iremos usar a API de paginação para navegar suas respostas.

Teremos o seguinte caso:

  • Começar na página 0 com 5 itens
  • Um botão para adicionar 1 a página
  • Um botão para subtrair 1 a página
  • Listar os resultados

O exemplo complete ficaria assim:

function App() {
  let [state, setState] = React.useState({
    status: "idle",
    page: -1,
    cats: [],
    error: ""
  });

  React.useEffect(() => {
    if (state.page < 0) {
      return;
    }

    let didRun = true;

    setState((prevState) => ({ ...prevState, status: "pending", error: "" }));

    let setCats = (cats) => {
      if (didRun) {
        setState((prevState) => ({ ...prevState, status: "done", cats }));
      }
    };
    let setError = (error) => {
      if (didRun) {
        setState((prevState) => ({ ...prevState, status: "error", error }));
      }
    };

    let url = `https://api.thecatapi.com/v1/images/search?limit=5&page=${state.page}&order=Desc`;
    let controller = new AbortController();

    fetch(url, { signal: controller.signal })
      .then((res) => res.json())
      .then(setCats)
      .catch(setError);

    return () => {
      didRun = false;
      controller.abort();
    };
  }, [state.page]);

  let updateBy = (value) => (event) => {
    event.preventDefault();
    setState((prevState) => ({ ...prevState, page: prevState.page + value }));
  };

  return (
    <div className="App">
      <div>
        <button onClick={updateBy(-1)}>-1</button>
        <span> - </span>
        <button onClick={updateBy(+1)}>+1</button>
        <p>{state.status}</p>
        <p>{state.error.message}</p>
      </div>
      <div className="Cats">
        {state.cats.map((cat) => {
          return (
            <div key={cat.id}>
              <img width="96" height="96" src={cat.url} />
            </div>
          );
        })}
      </div>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Visualmente teríamos:

Ao clicar em -1 e +1 rapidamente, podemos ver as requisições canceladas na aba Network do DevTools do seu navegador:

Finalizando

Você pode encontrar o exemplo complete no meu CodeSandbox:

https://codesandbox.io/s/cancel-fetch-using-abort-api-ktvwz

Ao discutirmos qual seria a melhor opção para evitar uma quantidade absurda de requisições desnecessárias pelo clique do usuário, usar AbortController talvez não seja a melhor opção. As práticas atuais ainda são válidas.

Em outros casos onde a duplicação de requests pode acontecer ao montar/desmontar um componente, utilizar o AbortController pode ajudar no desempenho no lado do cliente.


Qualquer pergunta, estou no Twitter: https://twitter.com/oieduardorabelo

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

Cloudinary image

Video API: manage, encode, and optimize for any device, channel or network condition. Deliver branded video experiences in minutes and get deep engagement insights.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay