Criando react hook personalizado para fazer requisições
Tags: react, hooks, request, typescript
Published: 2023-01-27 00:08:23

## 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:

```ts
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:

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

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

Agora vamos criar nosso estado inicial:

```tsx
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`:

```tsx
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.

```tsx
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:

```tsx
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>
  );
};
```