React Hook - useAsync


O useAsync é um React Hook personalizado que simplifica a lógica associada à execução de funções assíncronas em componentes React. Ele encapsula o gerenciamento de estados relacionados a operações assíncronas, proporcionando uma abordagem mais limpa e reutilizável para lidar com a assincronicidade no React.

Vamos explorar a implementação do useAsync passo a passo:

1. Importação de Hooks

import { useCallback, useEffect, useState } from 'react';

O useAsync utiliza useCallback, useEffect, e useState do React.

2. Estado Interno do Hook

const [state, setState] = useState({
  result: null,
  error: null,
  status: 'idle',
});

O estado interno do hook (state) mantém informações sobre o resultado da operação assíncrona (result), qualquer erro ocorrido (error), e o status atual da operação (status), que começa como ‘idle’.

3. Função run e useCallback

const run = useCallback(() => {
  setState({
    result: null,
    error: null,
    status: 'pending',
  });
 
  return asyncFunction()
    .then((response) => {
      setState({
        result: response,
        error: null,
        status: 'settled',
      });
    })
    .catch((err) => {
      setState({
        result: null,
        error: err,
        status: 'error',
      });
    });
}, [asyncFunction]);

A função run é responsável por iniciar a operação assíncrona. Ela atualiza o estado para refletir o início ('pending') e, após a conclusão ou erro, atualiza o estado para refletir o resultado ('settled') ou ('error'), respectivamente. Utiliza-se useCallback para garantir que a função não seja recriada em cada renderização, otimizando o desempenho.

4. Efeito para executar a função assíncrona

useEffect(() => {
  if (shouldRun) {
    run();
  }
}, [run, shouldRun]);

Este efeito é acionado quando shouldRun muda. Se for verdadeiro, chama a função run, iniciando a execução da operação assíncrona.

5. Retorno do Hook

return [run, state.result, state.error, state.status];

O useAsync retorna um array contendo a função run para iniciar a operação, o resultado (result), o erro (error), e o status atual (status). Esses valores podem ser utilizados no componente React que utiliza este hook.

6. Código

import { useCallback, useEffect, useState } from 'react';
 
const useAsync = (asyncFunction, shouldRun) => {
  const [state, setState] = useState({
    result: null,
    error: null,
    status: 'idle',
  });
 
  const run = useCallback(() => {
    setState({
      result: null,
      error: null,
      status: 'pending',
    });
 
    return asyncFunction()
      .then((response) => {
        setState({
          result: response,
          error: null,
          status: 'settled',
        });
      })
      .catch((err) => {
        setState({
          result: null,
          error: err,
          status: 'error',
        });
      });
  }, [asyncFunction]);
 
  useEffect(() => {
    if (shouldRun) {
      run();
    }
  }, [run, shouldRun]);
 
  return [run, state.result, state.error, state.status];
};

Exemplo de uso

const fetchData = async () => {
  const data = await fetch('https://jsonplaceholder.typicode.com/posts/');
  const json = await data.json();
  return json;
};
 
export const Home = () => {
  const [reFetchData, result, error, status] = useAsync(fetchData, true);
 
  function handleClick() {
    reFetchData();
  }
 
  if (status === 'idle') {
    return <pre>idle: Nothing running...</pre>;
  }
 
  if (status === 'pending') {
    return <pre>pending: Loading...</pre>;
  }
 
  if (status === 'error') {
    return <pre>error: {error.message}</pre>;
  }
 
  if (status === 'settled') {
	return <pre onClick={handleClick}>settled: {JSON.stringify(result, null, 2)}</pre>;
  }
 
  return <pre>Something went wrong!</pre>;
};

O useAsync é utilizado no componente Home para gerenciar uma chamada assíncrona à função fetchData. O resultado, erro e status são desestruturados para serem usados no componente.

Referências