Autenticação no React com Context API e Hooks
Tags: react, reactnative, tutorial, authentication
Published: 2020-08-31 03:18:56

# O que é Context API?

A Context API, fornece uma maneira de passar os dados de componentes sem ter que passar manualmente em todos os níveis. Algo como o que o Redux faz criando e gerenciando um estado global, inclusive o próprio Redux utiliza Context API por debaixo dos panos.

Vale a pena dar uma olhada em como funciona e utilizar nos seus projetos para manipular dados simples, como na autenticação de um usuário em que veremos neste post.

# Criando novo projeto

Primeiramente vou criar um novo projeto seguindo a documentação do ReactJS com Typescript (caso não deseje utilizar Typescript pode seguir normalmente o tutorial e ignorar as declarações de tipos), então vou executar o seguinte comando no meu terminal:

```bash
$ npx create-react-app authapp --template typescript
```

Com o projeto criado e aberto no seu editor preferido vamos começar apagando todos os arquivos que o React cria automaticamente, ficando com a seguinte estrutura:

![estrutura](https://dev-to-uploads.s3.amazonaws.com/i/9o5pkuqu8yy5bfxfuzu6.png)

> Lembre-se de apagar as linhas de código que dependiam dos arquivos deletados!

Agora vamos instalar uma lib para nos auxiliar a lidar com as rotas da aplicação, para esse tutorial estarei utilizando a [React Router](https://reactrouter.com/web/guides/quick-start). Podemos instalar através do seguinte comando:

```bash
$ yarn add react-router-dom
```

Caso esteja utilizando Typescript igual a mim, vai precisar instalar também a definição de tipos para essa lib como dependência de desenvolvimento através do comando:

```bash
$ yarn add @types/react-router-dom -D
```


## Paginas da aplicação

Com a lib instalada podemos continuar, vamos agora criar dentro de `src` uma pasta chamada `pages` contendo 2 outras pastas, `Login` e `Home` cada uma com um arquivo `index.tsx` dentro, que serão as páginas da nossa aplicação. Por enquanto estamos assim:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vzaqdzxnngtujqpc8s3t.png)

Para ser mais rápido nesse tutorial não vou criar nenhum tipo de estilo para as páginas, mas fique a vontade para isso! Em nossa página Home vamos criar um componente contendo somente um `h1` com o nome da página:

```TSX
import React from 'react';
 
const Home: React.FC = () => {
 return (
   <div>
     <h1>Home</h1>
   </div>
 );
};
 
export default Home;

```

Em nossa página de Login vamos criar somente um botão que será responsável pelo nosso Login:

```TSX
import React from 'react';
 
const Login: React.FC = () => {
 function handleLogin() {}
 
 return (
   <div>
     <button onClick={handleLogin}>Login</button>
   </div>
 );
};
 
export default Login;
```

## Rotas da aplicação

Com as páginas criadas, vamos agora criar as rotas da nossa aplicação. Primeiro vamos criar,dentro de `src`,  uma pasta `routes` onde vamos criar os arquivos que serão nossa rotas. 

Para esse tutorial criei um arquivo que será responsável pelas rotas em que o usuário poderá se autenticar, tais como Login, Sign Up, etc, e um outro arquivo que será responsável pela navegação depois do usuário já estar autenticado. Ficando com a seguinte estrutura:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/oj2fpg4z9m1d3c3vecrd.png)

No nosso arquivo `SignRoutes.tsx` vamos criar a rota para nossa página de login seguindo a documentação do React Router.

```TSX
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
 
import Login from '../pages/Login';
 
const SignRoutes: React.FC = () => {
 return (
   <BrowserRouter>
     <Route path="/" component={Login} />
   </BrowserRouter>
 );
};
 
export default SignRoutes;
```

Vamos fazer o mesmo para nosso `OtherRoutes.tsx` mas dessa vez utilizando nossa página Home:

```TSX
import React from 'react';
import { BrowserRouter, Route } from 'react-router-dom';
 
import Home from '../pages/Home';
 
const OtherRoutes: React.FC = () => {
 return (
   <BrowserRouter>
     <Route path="/" component={Home} />
   </BrowserRouter>
 );
};
 
export default OtherRoutes;
```

Agora em nosso `index.tsx`, ainda na pasta, `routes` vamos importar nossas rotas e por enquanto retornar somente nossa rota de Login:

```TSX
import React from 'react';
 
import SignRoutes from './SignRoutes';
import OtherRoutes from './OtherRoutes';
 
const Routes: React.FC = () => {
 return <SignRoutes />;
};
 
export default Routes;
```

Agora em nosso `App.tsx` na raiz do nosso projeto importaremos nossas Rotas, assim:

```TSX
import React from 'react';
import Routes from './routes';
 
function App() {
 return <Routes />;
}
 
export default App;
```

Se rodarmos `yarn start` no nosso terminal poderemos ver nossa página de Login com um botão:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/rbddgluhsbvmpno0y3iq.png)

# Criando Contexto

Com a base da nossa aplicação pronta, vamos começar a utilizar o Contexto do React para criar um “estado global” e criar nossa autenticação. Para isso dentro do nosso `src` vamos criar uma pasta `contexts` com um arquivo `auth.tsx`:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/fwfnvcv8x23fhtsdw69j.png)

Dentro do nosso `auth.tsx`vamos importar o `createContext` do React e exportar uma variável `AuthContext`, um contexto com um objeto vazio dentro:

```TSX
import React, { createContext } from 'react';
 
const AuthContext = createContext({});
 
export default AuthContext;
```

Em nosso `App.tsx` vamos importar esse `AuthContext` e circundar nossas rotas com o Provider do nosso Contexto passando uma propriedade `value` com um objeto contendo `signed: true`, da seguinte forma:

```TSX
import AuthContext from './contexts/auth';
 
function App() {
 return (
   <AuthContext.Provider value={{signed: true}}>
     <Routes />
   </AuthContext.Provider>
 );
}
``` 

Agora se em nossa página de Login buscarmos esse Contexto e dermos um console.log teremos a seguinte resposta:

```TSX
import React, { useContext } from 'react';
import AuthContext from '../../contexts/auth';
 
const Login: React.FC = () => {
 const context = useContext(AuthContext);
 
 console.log(context);
...
```

Console.log:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/dgdwluo3r6r49ra356vu.png)

Ou seja, nosso signed enviado no nosso App.tsx pode ser recuperado dentro do nosso componente! 

## Criando Provider

Para melhorar nosso contexto e implementar o restante do código para lidar com a autenticação, vamos trazer o Provider para dentro do nosso arquivo `auth.tsx` e o exportá-lo.
```TSX
const AuthContext = createContext({});
...
export const AuthProvider: React.FC = ({ children }) => {
 return (
   <AuthContext.Provider value={{ signed: true }}>
     {children}
   </AuthContext.Provider>
 );
};
...
export default AuthContext;
```
Agora podemos importar nosso provider dentro do `App.tsx`, melhorando bastante o nosso código sem alterar o funcionamento:

```TSX
...
import { AuthProvider } from './contexts/auth';
 
function App() {
 return (
   <AuthProvider>
     <Routes />
   </AuthProvider>
 );
}
...
```

### Fazendo as chamadas a API

Utilizarei [Axios](https://github.com/axios/axios) para realizar as requisições à API. Para isso vamos instalar o pacote do axios:

```bash
yarn add axios
```

Vamos criar uma pasta `services` e um arquivo `api.ts` para configurar o axios:

```TS
import axios from 'axios';
 
const api = axios.create({
 baseURL: 'https://localhost:3333',
});
 
export default api;
```

Com o axios configurado vamos criar uma função para fazer a chamada a api dentro do nosso arquivo `auth.tsx`:

```TSX
...
import api from '../services/api';
...
export const AuthProvider: React.FC = ({ children }) => {
...
async function Login() {
   const response = await api.post('/login', {
     email: 'example@email.com',
     password: '123456',
   });
 
   console.log(response);
 }
...
```

Para utilizarmos essa função em outros componentes teremos que adicioná-la ao value do nosso Provider:

```TSX
return (
   <AuthContext.Provider value={{ signed: true, Login }}>
...
```

Criaremos também uma interface com os dados que estarão no nosso value e adicionaremos o tipo criado ao nosso contexto:

```TS
interface AuthContextData {
 signed: boolean;
 Login(): Promise<void>;
}
 
const AuthContext = createContext<AuthContextData>({} as AuthContextData);
```

E agora poderemos acessá-la no nosso componente de Login e realizar o login:

```TSX
...
function handleLogin() {
   context.Login();
}
...
```
Agora ao clicarmos no botão de login nossa função responsável pelo login será chamada e para ter certeza disso podemos ver nosso console.log:

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/wzrfzxlcv8d1cqdvi749.png)

Precisamos guardar os dados retornados pela API em algum lugar, para isso vamos criar um estado para nosso `user` e vamos adicionar nosso `token` no header das nossas chamadas pelo axios:

```TSX
...
  const [user, setUser] = useState<object | null>(null);
...
  async function Login() {
...
   setUser(response.data.user);
   api.defaults.headers.Authorization = `Bearer ${response.data.token}`
...
```

Com nosso user em mãos podemos adicioná-lo ao Provider e mudar nosso signed para depender de user:


```TSX
...
return (
   <AuthContext.Provider value={{ signed: Boolean(user), user, Login }}>
     {children}
   </AuthContext.Provider>
 );
...
```

Lembre-se de adicionar o user a interface `AuthContextData` caso esteja utilizando typescript:

```TSX
interface AuthContextData {
  signed: boolean;
  user: object | null;
  Login(): Promise<void>;
}
```

## Modificando as Rotas

Finalmente podemos ir no `index.tsx` das nossas rotas e utilizar nosso contexto para decidir qual rota o usuário deve acessar:

```TSX
import React, { useContext } from 'react';
import AuthContext from '../contexts/auth';
 
import SignRoutes from './SignRoutes';
import OtherRoutes from './OtherRoutes';
 
const Routes: React.FC = () => {
 const { signed } = useContext(AuthContext);
 
 return signed ? <OtherRoutes /> : <SignRoutes />;
};
 
export default Routes;
```

Com isso pronto, nossa autenticação já está funcionando e ao clicar em Login o usuário deve ser mandado para a página Home!

## Criar hook useAuth

Podemos criar um hook personalizado para facilitar o uso do nosso contexto, para isso vamos exportar uma função chamada useAuth do nosso arquivo `auth.tsx`, que cria nosso contexto com `useContext`, e remover nosso export default de AuthContext:

```TSX
export function useAuth(){
 const context = useContext(AuthContext);
 
 return context;
}
```

Agora podemos mudar nas rotas e na nossa página de login onde utilizamos `useContext(AuthContext)` para:

```TSX
import { useAuth } from '../../contexts/auth';
...
 const context = useAuth();
...
```

Finalmente nosso hook para autenticação está pronto para uso!

---

# Extras

## Salvar dados no Storage

Normalmente salvamos os dados como `user` e `token` para manter o usuário logado mesmo após sair da aplicação. Para isso podemos utilizar o SessionStorage ou LocalStorage, na Web, e o [AsyncStorage](https://github.com/react-native-community/async-storage) no React Native.

Na nossa função de login no `auth.tsx` podemos fazer o seguinte:

```TSX
async function Login(){
...

  localStorage.setItem('@App:user', JSON.stringify(response.data.user));
  localStorage.setItem('@App:token', response.data.token);
}
...
```

Para recuperar esses dados podemos criar um useEffect dentro do nosso componente `AuthProvider`:

```TSX
...
export const AuthProvider: React.FC = ({ children }) => {
...

useEffect(() => {
    const storagedUser = localStorage.getItem('@App:user');
    const storagedToken = localStorage.getItem('@App:token');

    if (storagedToken && storagedUser) {
      setUser(JSON.parse(storagedUser));
      api.defaults.headers.Authorization = `Bearer ${storagedToken}`;
    }
  }, []);
...
```

## Função de Logout

Como agora estamos salvando os dados no localStorage precisamos de uma forma para fazer logout da aplicação, para isso no nosso Provider dentro de `auth.tsx` podemos criar uma função que seta user como null novamente e remove os items do localStorage:

```TSX
...
interface AuthContextData {
  signed: boolean;
  user: object | null;
  Login(user: object): Promise<void>;
  Logout(): void;
}
...
export const AuthProvider: React.FC = ({ children }) => {
...
  function Logout() {
    setUser(null);

    sessionStorage.removeItem('@App:user');
    sessionStorage.removeItem('App:token');
  }

  return (
    <AuthContext.Provider
      value={{ signed: Boolean(user), user, Login, Logout }}
    >
      {children}
    </AuthContext.Provider>
  );
...
```

Podemos agora criar um botão em nossa página Home e chamar essa função para realizar o logout da aplicação:

```TSX
const Home: React.FC = () => {
  const { Logout } = useAuth();

  async function handleLogout() {
    Logout();
  }

  return (
    <div>
      <h1>Home</h1>
      <button onClick={handleLogout}>Logout</button>
    </div>
  );
};
```
---

Apesar de parecer complicado de inicio, podemos ver que no fim temos algo bem mais simples que Redux e funcionando como deveria! O que achou? 

Todo código pode ser encontrado no Github: https://github.com/rafacdomin/Auth-React-ContextAPI

Fonte: https://www.youtube.com/watch?v=KISMYYXSIX8