loading...
Cover image for Autenticação no React com Context API e Hooks

Autenticação no React com Context API e Hooks

rafacdomin profile image Rafael Domingues ・7 min read

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:

$ 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

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. Podemos instalar através do seguinte comando:

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

$ 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

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:

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:

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

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

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:

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:

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:

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

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

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

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:

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:

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

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.

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:

...
import { AuthProvider } from './contexts/auth';

function App() {
 return (
   <AuthProvider>
     <Routes />
   </AuthProvider>
 );
}
...

Fazendo as chamadas a API

Utilizarei Axios para realizar as requisições à API. Para isso vamos instalar o pacote do axios:

yarn add axios

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

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:

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

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:

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:

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

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:

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

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

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:

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:

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:

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 no React Native.

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

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:

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

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

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

Posted on by:

rafacdomin profile

Rafael Domingues

@rafacdomin

# Full-Stack Developer 👨‍💻 # Engineering Student ⚙️ Passionate about using technology to change people's lives 💜

Discussion

pic
Editor guide