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:
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:
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:
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:
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
:
Dentro do nosso auth.tsx
vamos 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:
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:
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
Top comments (17)
Conteúdo Incrível, me ajudou demais, muito obrigado de verdade!!
Em auth.tsx na linha
export const AuthProvider: React.FC = ({ children } ) => {
Está dando o seguinte erro de typescript: A propriedade 'children' não existe no tipo '{}'.
Resolvi da seguinte forma: - criei uma interface:
interface Props {
children: React.ReactNode;
}
E implementei-a assim:
export const AuthProvider: React.FC<Props> = ({ children }: Props ) => {
Cara ajudou muito obrigado
Excelente explicação Rafael, muito bom mesmo! Deu tudo certo na minha aplicação!
Só estou com um "probleminha" que talvez você possa ajudar (ainda mais, kkk).
Quando faço o login, a home carrega normalmente, com os dados do usuário no caminho "/", mas quando clico no refresh da página, os dados do usuário desaparecem e só aparecem de novo se eu sair e logar novamente.
Eu chamei os dados do usuário com "const { user } = useAuth();", se eu coloco dentro de um useEffect, não aceita....tem alguma alternativa pra isso?
Já consegui... lerdeza minha mesmo kkkk
Lá no auth tava fazendo o setItem do @App:user com "response.data.name" kkkk foi só tirar o ".name" e deu certo kkkk
Mas valeu pelo post mesmo assim! Grande ajuda!
Excelente post, foi de grande ajuda!
Olá Rafael. Gostei bastante da sua explicação.
Estou utilizando o tutorial para implementar em um projeto mas não uso typescript, então devo simplesmente ignorar a interface?
AuthContextData {
signed: boolean;
Login(): Promise;
}
E essa constante como ficaria?
const AuthContext = createContext({} as AuthContextData);
Obrigado.
Muito bom o tutorial, fiz baseado nele usando api do Google Login e funcionou de boa consigo puxar até a foto do user nessa api, mas questão de segurança só armazenando token é o recomendado ? Valeuuu
Cara esse link que vc coloca na API /login, seria do que ? não consegui entender essa parte. Agradeço
É só a rota onde faço autenticação na minha api, neste caso localhost:3333 é a url base da minha api e /login é a rota para autenticação. Como já havia definido na configuração no axios a url base como localhost:3333 preciso somente informar a rota que desejo acessar, então digitei /login em vez de localhost:3333/login
ao dar refresh eu perco o login =/
Verifica a parte sobre Salvar dados no Storage, a ideia é basicamente salvar o token do usuário no localStorage do navegador após o login ser feito e sempre que o usuário acessar a página buscar esse token no localStorage novamente
Cara, o meu auth.tsx ta apontando erro.
Você sabe qual o problema?
Qual erro que está acontecendo?