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
ouerror
, 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 pelofetch
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>
);
};