Executando verificação de segurança...
2

Criando react hook personalizado para fazer requisições

Introdução

Este tutorial mostrará como criar um hook customizado chamado useFetcher usando o useReducer, ele vai permitir que façamos requisições HTTP de maneira fácil além de gerenciarmos o estado dessas requisições.

Imports

Vamos começar criando um arquivo chamado useFetcher.ts, gosto de colocar esses custom hooks dentro de src/hooks, agora vamos importar as dependências necessárias:

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

Criar hook useFetcher

Em seguida, vamos criar uma função chamada useFetcher que possui um parâmetro para a URL que desejamos fazer a requisição, essa função terá também um generic que vai auxiliar na tipagem desse hook, e o tipo StateProps representa o que será retornado desse hook:

type StateProps<T> = {
  data?: T;
  loading: boolean;
  error?: Error;
};

function useFetcher<T = unknown>(url: string): StateProps<T> {
  ...
}

Agora vamos criar nosso estado inicial:

function useFetcher<T = unknown>(url: string): StateProps<T> {
  const initialState: StateProps<T> = {
    data: undefined,
    loading: true,
    error: undefined,
  };
}

Certo agora precisamos criar nossa função de reducer, ela será responsável por conter nosso state e action, no state vamos ter acesso ao data, loading e error e no action teremos acesso ao type e payload:

type ActionType<T> =
  | { type: 'loading' }
  | { type: 'fetched'; payload: T }
  | { type: 'error'; payload: Error };

function useFetcher<T = unknown>(url: string): StateProps<T> {
  ...

  function reducer(state: StateProps<T>, action: ActionType<T>) {
    switch (action.type) {
      case 'loading':
        return { ...state, loading: true } as StateProps<T>;
      case 'fetched':
        return {
          ...state,
          data: action.payload,
          loading: false,
        } as StateProps<T>;
      case 'error':
        return {
          ...state,
          error: action.payload,
          loading: false,
        } as StateProps<T>;
      default:
        return state;
    }
  }
}

Basicamente, na função acima usamos o switch para verificar se o type disparado é loading, fetched ou error, e com isso retornamos um objeto, com a cópia do estado e as modificações necessárias.

Agora vamos criar nosso handleFetch, ele será responsável por realizar de fato a requisição, além disso ele realiza dispatch do type necessário em cada momento da chamada a API:

  • 1º - Ao executar a função ele dispara o type: loading.

  • 2º - Ao finalizar com sucesso a requisição ele dispara o type: fetched + payload, esse payload é os dados retornados da API.

  • 3º - Em caso de erro na requisição ele dispara o type: error + payload, esse payload será o erro que foi retornado.

function useFetcher<T = unknown>(url: string): StateProps<T> {
  ...

  const [state, dispatch] = useReducer(reducer, initialState);

  const handleFetch = useCallback(async () => {
    dispatch({ type: 'loading' });

    try {
      const response = await axios.get<T>(url);

      dispatch({ type: 'fetched', payload: response.data });
    } catch (error) {
      if (axios.isAxiosError(error) || error instanceof Error) {
        dispatch({ type: 'error', payload: error });
      }
    }
  }, [url]);

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

  return {
    data: state.data,
    loading: state.loading,
    error: state.error,
  };
}

obs: utilizei como exemplo o axios para realizar a requisição, mas poderia ser trocado pelo fetch normalmente.

Testando useFetcher

Por fim, só falta testar o nosso custom hook:

import { useFetcher } from '../hooks/useFetcher';

interface ITodo {
  userId: number;
  id: number;
  title: string;
  completed: boolean;
}

const Home = () => {
  const {
    loading: loadingTodos,
    data: todosData,
    error: todosError,
  } = useFetcher<ITodo>('https://jsonplaceholder.typicode.com/todos/1');

  return (
    <div>
      {loadingTodos && <h1>Loading...</h1>}

      {todosData && <pre>{JSON.stringify(todosData, null, 2)}</pre>}

      {todosError && <h1>{todosError?.message}</h1>}
    </div>
  );
};
Carregando publicação patrocinada...