Quem já trabalhou com rotas com Expo ou React Native, sabe que o React Navigation é a forma padrão de lidar com navegação! No entanto, conforme mais telas você vai criando no seu app, o código utilizado pode ficar bem grande, com processos manuais que precisam de muitas linhas de código para serem feitos. Logo, o time do Expo lançou uma novidade: Expo Router, uma alternativa ao React Navigation para roteamento e navegação com React Native.
Esse post irá mostrar como criar um fluxo de autenticação com Expo Router no seu app React Native.
Índice
generated with Summaryze Forem 🌱
Principais funcionalidades do Expo Router:
- Nativo: Construído em cima do React Navigation, a navegação do Expo Router é nativa e otimizada.
- Compartilhável: Toda tela do seu aplicativo é automaticamente configurada com deep linking. Tornando qualquer rota do seu aplicativo compartilhável com links, como por exemplo, um link de redirecionamento de uma notificação, não sendo necessário configurar manualmente quais telas são acessíveis por links.
- Offline-first: Os aplicativos são armazenados em cache e executados de forma offline first, com atualizações automáticas quando você publica uma nova versão.
O Expo Router trás um conceito de file based routing, ou seja, as rotas da aplicação refletem a estrutura dos arquivos criados na pasta /app
. Parecido, por exemplo, com a estrutura do Next.js.
Quando um arquivo é criado na pasta app, automaticamente se torna uma rota no app. Logo, diferentemente do React Navigation, todas as stacks (ou rotas) do seu app já ficam publicas e disponíveis para navegação! Além disso, as navegações para diferentes telas podem ser feitas usando um elemento <Link href='/nome-da-rota'
, por exemplo. bem parecido com o que fazemos na web. Você pode usar um router.replace('nome-da-rota') para definir globalmente o redirecionamento de rota, além de rotas dinâmicas, criação de grupos de rotas e muito mais. 🤯
Pré-requisitos
Para o exemplo a seguir, você pode clonar o projeto para reproduzir o exemplo.
git clone git@github.com:lumamontes/expo-router-auth.git
Instale as dependências do projeto:
npx expo install
E rode o projeto:
npx expo start
Mas, caso queira criar um projeto do zero, você pode seguir os passos originais do Expo Router.
Exemplo de código
Agora, vamos a um exemplo no código! Vamos implementar um fluxo de autenticação utilizando o Expo Router para navegação. Vamos analisar a estrutura do projeto:
Alguns pontos chaves para entender o Expo Router:
Todos os arquivos que estão na pasta
/app
serão rotas (com exceção de arquivos "especiais", que irei comentar abaixo!)_layout.tsx
: O expo router possibilita a criação de Layout routes, elementos que são compartilhados entre diversas telas, como uma header. No caso do arquivo app/_layout.tsx, ele é um layout que será compartilhado entre todas as telas dentro da pasta/app
.Pastas
(auth)
e(tags)
: são grupos de rotas do Expo Router, que são usados para organizar as rotas do app. Você pode adicionar quantos grupos quiser. Grupos também são bons para organizar seções do app. No exemplo a seguir, temos app/(auth) que irá ter as telas para usuários autenticados. Um ponto interessante é que os grupos não afetam a url, ou seja, um arquivo localizado na pastaapp/(auth)/nome-do-arquivo.tsx
não terá a urlapp/(auth)/nome-do-arquivo
, mas simapp/nome-do-arquivo
.ctx.tsx
: é um contexto de autenticação, que irá centralizar algumas informações globais como a sessão do usuário, a função de logar e deslogar.+not-found.tsx
: é uma rota que será acessada quando nenhuma outra rota for encontrada.
Logo, o nosso app irá possui duas camadas:
- Rotas para usuários autenticados (todo o conteúdo do grupo (auth))
- Rotas para usuários não autenticados (somente a tela de login)
Para fazer essa separação entre a camada de usuários autenticados e não autenticados, é possível criar um contexto que vai definir essas regras de negócio:
1 - Crie um contexto de autenticação
Esse contexto irá centralizar algumas informações e lógicas globais como a sessão do usuário, a função de signIn e signOut.
iimport React from "react";
import { useStorageState } from "./useStorageState";
const AuthContext = React.createContext<{
signIn: () => void;
signOut: () => void;
session?: string | null;
isLoading: boolean;
}>({
signIn: () => null,
signOut: () => null,
session: null,
isLoading: false,
});
// This hook can be used to access the user info.
export function useSession() {
const value = React.useContext(AuthContext);
return value;
}
export function SessionProvider(props: React.PropsWithChildren) {
const [[isLoading, session], setSession] = useStorageState("session");
return (
<AuthContext.Provider
value={{
signIn: () => {
// Add your login logic here
// For example purposes, we'll just set a fake session in storage
setSession("John Doe");
},
signOut: () => {
setSession(null);
},
session,
isLoading,
}}
>
{props.children}
</AuthContext.Provider>
);
}
A função signIn
irá salvar a sessão do usuário, nesse caso, uma simples string 'John Doe', e a função signOut
irá remover a sessão alterando esse valor para null.
Para finalidade de testes, a função signIn não irá ter nenhum tipo de validação, sempre que ela for chamada, o usuário será autenticado. No entanto, em um app real, você pode adicionar a lógica de login do seu app fazendo uma requisição para uma API, por exemplo.
Um ponto importante é que iremos salvar essas informações de sessão do usuário no storage do dispositivo, para que o usuário não precise logar toda vez que abrir o app.
Para isso, utilizaremos o hook useStorageState
que retornar um estado e uma função para alterar esse estado, salvar e recuperar as informações do storage do dispositivo utilizando a biblioteca expo-secure-store
.
Esse hook é composto por três funções:
- A função
useAsyncState
irá retornar um estado e uma função para alterar esse estado, mas com um estado inicial de loading, que será true, e o valor inicial, que será null.
import * as SecureStore from "expo-secure-store";
import * as React from "react";
import { Platform } from "react-native";
type UseStateHook<T> = [[boolean, T | null], (value: T | null) => void];
function useAsyncState<T>(
initialValue: [boolean, T | null] = [true, null]
): UseStateHook<T> {
return React.useReducer(
(
state: [boolean, T | null],
action: T | null = null
): [boolean, T | null] => [false, action],
initialValue
) as UseStateHook<T>;
}
-
setStorageItemAsync
: que irá salvar ou deletar um item no storage do dispositivo, dependendo do valor passado.
export async function setStorageItemAsync(key: string, value: string | null) {
if (value == null) {
await SecureStore.deleteItemAsync(key);
} else {
await SecureStore.setItemAsync(key, value);
}
}
-
useStorageState
: que irá retornar um estado e uma função para alterar esse estado, salvando e recuperando as informações do storage do dispositivo utilizando a bibliotecaexpo-secure-store
.
export function useStorageState(key: string): UseStateHook<string> {
const [state, setState] = useAsyncState<string>();
React.useEffect(() => {
SecureStore.getItemAsync(key).then((value) => {
setState(value);
});
}, [key]);
const setValue = React.useCallback(
(value: string | null) => {
setState(value);
setStorageItemAsync(key, value);
},
[key]
);
return [state, setValue];
}
Logo, se eu usar o hook dessa forma: useStorageState('session'), ele irá retornar: isLoading, session e setSession.
Mas se eu usasse dessa forma: useStorageState('user'), ele irá retornar: isLoading, user e setUser, por exemplo.
Podemos então entender que o useStorageState é um hook genérico que irá retornar um estado e uma função para alterar esse estado, salvando e recuperando as informações do storage do dispositivo utilizando a biblioteca expo-secure-store
.
2 - Adicione o arquivo app/_layout.tsx
Esse arquivo é um layout que será compartilhado entre todas as telas do app. Nele, podemos:
- Definir a rota inicial do app
export const unstable_settings = {
initialRouteName: "login",
};
Essa simples linha de código irá garantir que a rota inicial do nosso app será sempre 'login', ou seja, o componente presente no arquivo de login.tsx!
- Adicionar o nosso contexto por volta de todo o seu app
Ele irá apenas retornar o children, que será a stack do grupo (auth)
ou a tela de login, dependendo se o usuário está autenticado ou não. Para isso, usaremos o <Slot>
do Expo Router, que é um elemento que irá renderizar o "children" da rota atual:
import { Slot } from "expo-router";
import { SessionProvider } from "./ctx";
export default function RootLayoutNav() {
return (
<SessionProvider>
<Slot />
</SessionProvider>
);
}
3 - Crie a tela de login
Na tela de login, que será o arquivo app/login.tsx
, você pode adicionar a lógica de login do seu app, como um formulário de email e senha. No nosso caso, vamos somente um botão que irá logar o usuário no app.
export default function Login() {
const { signIn } = useSession();
const handleLogin = () => {
signIn();
//Antes de navegar, tenha certeza de que o usuário está autenticado
router.replace("/");
};
return (
<View>
<Button title="Login" onPress={handleLogin} />
</View>
);
}
Isso irá redirecionar para (auth)/\_layout.tsx
!
4 - Adicione a lógica de autenticação no arquivo app/(auth)/\_layout.tsx
Esse arquivo é um layout que será compartilhado entre todas as telas do app no grupo (auth), ou seja, todas as telas que precisam de autenticação para serem acessadas. Nele, podemos adicionar a lógica de autenticação do app:
Se o usuário não possuir sessão, redirecionar para a tela de login
Caso o usuário tenha sessão, retornar a stack do grupo
(tabs)
, que contém o restante das telas do app.
const { session } = useSession();
if (!session) {
return <Redirect href="/login" />;
}
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="modal" options={{ presentation: "modal" }} />
</Stack>
);
Assim, ao clicar para login e ser redirecionado com sucesso, o usuário irá ter acesso a tela app/(auth)/(tabs)/index.tsx
!
E caso o usuário não esteja autenticado, ele será redirecionado para a tela de login, completando um fluxo básico de autenticação.
Conclusão
Fazendo um comparativo com React Navigation, acredito que o Expo Router é bem acessível e fácil de entender, principalmente pra quem vem do desenvolvimento de frontend web.
Onde no React Navigation, várias funcionalidades eram feitas de forma manual, como a criação das rotas, a configuração de deep linking, entre outras, esses processos super importantes são feitos já automaticamente pelo Expo Router, deixando o código bem mais limpo. Pelo fato de ser file based routing, a estrutura do projeto fica bem mais organizada, e a navegação entre telas é bem mais simples, com a utilização do elemento <Link href='/nome-da-rota'
.
No geral, acredito que o Expo Router é uma ótima ferramenta para quem está começando a desenvolver apps com react native, ou para quem já tem experiência com react native e quer otimizar o tempo de desenvolvimento e manter uma navegação de fácil implementação. Estarei utilizando nos meus novos projetos com Expo 🚀
Segue o link do projeto no github, caso queira testar o código:
Top comments (4)
Excelente artigo, mas fiquei com uma duvida, como eu poderia atualizar o valor do isLoading? nessa demonstração mostra apenas como usar e não como settar ele, acabei ficando com duvida.
Não faz muito sentido tu alterar o valor do isLoading manualmente, ele só é usando enquanto os valores da session estão sendo capturados do secure store/localStorage. Como a função do expo secure storage é assíncrona, é necessário termos o estado de loading pra colocar na tela um componente tipo "está carregando, aguarde", para só depois do valor ser pego do local storage decidir mandar o usuário pras rotas privadas ou publicas.
Caracaaa, foi mal na demora @donpsantos, não tinha visto tua dúvida!
Mas é exatamente como o @marlonx19 falou! O valor do isLoading é atualizado de forma automática nesse código de demonstração. E aí esse valor é alterado de acordo com a informação ter sido recuperada da biblioteca expo-secure-storage.
Toooda essa batucada fica na função
useStorageState
:Por exemplo, se tu usar assim:
const [[isLoading, session], setSession] = useStorageState("session");
E dando um olho na função useStorageState:
Arrasou no artigo! Muito bacana usar o expo router no projeto