# Context Api
O Context API do React é uma solução nativa para compartilhar dados de maneira eficiente entre componentes sem a necessidade de passar props manualmente por várias camadas de componentes. Ele é útil especialmente quando certos dados ou estados precisam ser acessados em vários níveis da árvore de componentes, evitando o "prop drilling", que é o ato de passar dados por vários componentes intermediários desnecessariamente.
**Como funciona:**
* **Criação do Contexto**: Um contexto é criado usando a função `createContext`, o que gera um par de componentes: `Provider` e `Consumer`.
* **Provider**: O `Provider` é o componente responsável por "fornecer" os valores para todos os componentes que "consomem" aquele contexto. Ele envolve os componentes que devem acessar esses valores.
* **Consumer**: O `Consumer` pode ser usado para acessar os dados fornecidos pelo `Provider`. No entanto, o uso de `useContext`, um hook, é mais comum e simplificado.
Exemplo:
```typescript!
// context/authContext.tsx
import React, { createContext, useState, useContext } from 'react';
// 1. Criando o contexto
const AuthContext = createContext();
// 2. Criando o Provider
export const AuthProvider = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<AuthContext.Provider value={{ isLoggedIn, setIsLoggedIn }}>
{children}
</AuthContext.Provider>
);
};
// 3. Usando o contexto em outro componente
export const useAuth = () => useContext(AuthContext);
```
**Quando Usar o Context API?**
* Tema (dark mode/light mode): Troca de temas de uma aplicação.
* Autenticação: Compartilhar informações sobre o usuário logado entre várias páginas.
* Preferências globais: Idiomas, configurações de usuários, etc.
* Carrinho de compras: Manter o estado do carrinho de compras em um e-commerce acessível em várias páginas.
## Async Storage
O AsyncStorage é uma solução de armazenamento simples e assíncrona para dados persistentes no dispositivo. Ele é ideal para armazenar pequenas quantidades de dados, como tokens de autenticação ou preferências do usuário. O AsyncStorage armazena os dados no armazenamento interno do dispositivo, e esses dados permanecem disponíveis mesmo após o fechamento do app.
**Como Funciona:**
* **Gravando Dados**: Você pode salvar dados no formato chave-valor. Os valores são armazenados como strings.
* **Lendo Dados**: Os dados armazenados podem ser recuperados posteriormente, e, como o nome sugere, essas operações são assíncronas.
* **Removendo Dados**: Também é possível remover dados específicos ou limpar todo o armazenamento.
**Exemplo**
```typescript
import AsyncStorage from '@react-native-async-storage/async-storage';
// 1. Salvando um valor
const storeData = async (value) => {
try {
await AsyncStorage.setItem('@storage_Key', value);
} catch (e) {
// Error saving data
}
};
// 2. Lendo um valor
const getData = async () => {
try {
const value = await AsyncStorage.getItem('@storage_Key');
if(value !== null) {
// Value is retrieved
}
} catch(e) {
// Error reading value
}
};
// 3. Removendo um valor
const removeData = async () => {
try {
await AsyncStorage.removeItem('@storage_Key');
} catch(e) {
// Error removing value
}
};
```
# Passo a Passo
## AuthContext
Criando um authcontext para realizar o login e salvar os dados do usuario e o token com asyncStorage
### **authContext**
```typescript!
import {
createContext,
ReactNode,
useContext,
useEffect,
useState,
} from "react";
import AsyncStorage from "@react-native-async-storage/async-storage";
import axios from "axios";
import { User } from "../types/modelTypes";
interface AuthContextData {
user: User | null;
signIn: (email: string, password: string) => Promise<void>;
signOut: () => Promise<void>;
signed: boolean;
loading: boolean;
}
// Define o tipo dos props do <Provider></Provider>
interface AuthProviderProps {
children: ReactNode;
}
// Cria o contexto com um objeto vazio tipado
export const AuthContext = createContext<AuthContextData>(
{} as AuthContextData
);
export function AuthProvider({ children }: AuthProviderProps) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true); // Para indicar quando está carregando
// Carrega o token e os dados do usuário do AsyncStorage ao iniciar o app
useEffect(() => {
async function loadStorageData() {
const storedUser = await AsyncStorage.getItem("@Auth:user");
const storedToken = await AsyncStorage.getItem("@Auth:token");
if (storedUser && storedToken) {
setUser(JSON.parse(storedUser));
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${storedToken}`; // Define o token como padrão nas requisições
}
setLoading(false); // Carregamento finalizado
}
loadStorageData();
}, []);
const signIn = async (email: string, password: string) => {
try {
// Faz a requisição de login para o backend
const response = await axios.post("http://rotaApi:3000/auth/login", {
email,
password,
});
const { access_token, user } = response.data;
// Salva os dados no AsyncStorage
await AsyncStorage.setItem("@Auth:user", JSON.stringify(user));
await AsyncStorage.setItem("@Auth:token", access_token);
// Define o token para as futuras requisições
axios.defaults.headers.common["Authorization"] = `Bearer ${access_token}`;
// Atualiza o estado
setUser(user);
} catch (error) {
console.error("Erro durante o login:", error);
throw new Error("Falha na autenticação.");
}
};
const signOut = async () => {
// Remove os dados do AsyncStorage
await AsyncStorage.removeItem("@Auth:user");
await AsyncStorage.removeItem("@Auth:token");
// Reseta o estado
setUser(null);
};
return (
<AuthContext.Provider
value={{ user, signIn, signOut, signed: !!user, loading }}
>
{children}
</AuthContext.Provider>
);
}
// Hook personalizado para usar o contexto
export function useAuth(): AuthContextData {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth deve ser usado dentro de um AuthProvider");
}
return context;
}
```
- `AuthContextData` Interface:
Define os dados e funções disponíveis no contexto de autenticação: o usuário logado, funções de login (signIn) e logout (signOut), além de flags como signed (indica se o usuário está autenticado) e loading (indica se o app está carregando as informações de autenticação).
- `AuthProvider` Componente:
* **Estado interno**:
* user: Armazena os dados do usuário logado.
* loading: Indica se os dados de autenticação estão sendo carregados.
* **useEffect**:
* Ao iniciar o app, carrega o token e os dados do usuário do AsyncStorage e define o cabeçalho padrão de autenticação no Axios.
* **signIn** função:
* Realiza uma requisição de login para o backend com as credenciais do usuário.
* Armazena o token e os dados do usuário no AsyncStorage e configura o Axios para enviar o token em todas as requisições futuras.
* **signOut** função:
* Remove o token e os dados do usuário do AsyncStorage e reseta o estado local do usuário.
### Página de Login
```typescript
import { View, Text, TextInput, TouchableOpacity, Alert } from "react-native";
import { useState } from "react";
import { router } from "expo-router";
import { useAuth } from "../context/authContext";
export default function Login() {
const { signIn } = useAuth(); // Usando a função signIn do contexto de autenticação
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
async function handleLogin() {
if (email && password) {
try {
// Chama a função signIn e aguarda o login
await signIn(email, password);
router.replace("/home"); // Redireciona para a página inicial após login bem-sucedido
} catch (error) {
Alert.alert("Erro", "Credenciais inválidas!"); // Mostra um alerta em caso de erro
}
} else {
Alert.alert("Erro", "Preencha todos os campos!"); // Verifica se ambos campos foram preenchidos
}
}
// Código de formulário + Botão para chamar handleLogin
```
### Layout
Necessários adicionar ao rootlayout ou algum arquivo de entrada principal (camada mais externa)
```typescript
<AuthProvider>
<Stack>
<Stack.Screen name="index" options={{ title: "Login" }} />
<Stack.Screen name="home/index" options={{ title: "Home" }} />
<Stack.Screen name="pedidos/index" options={{ title: "Pedidos" }} />
</Stack>
</AuthProvider>
```
## ThemeContext
Este código encapsula a lógica de alternância de tema (claro/escuro) em um contexto reutilizável. O ThemeProvider provê o estado do tema e a função de alternância para todos os componentes dentro de sua árvore. Com isso, qualquer componente pode acessar e manipular o tema chamando o hook useTheme, sem necessidade de passar props explicitamente.
`ThemeContextData` Interface:
* Define os dados e funções disponíveis no contexto de tema:
* isDarkMode: Um booleano que indica se o tema escuro está ativado.
* toggleTheme: Função que alterna entre o tema claro e o tema escuro.
`ThemeProvider` Componente:
* **Estado interno**:
* isDarkMode: Controla se o tema atual é escuro (true) ou claro (false). Usa o useState para manter o valor.
* Função toggleTheme:
* Inverte o valor atual de isDarkMode, alternando entre os dois temas.
### themeContext
```typescript
// src/contexts/ThemeContext.tsx
import { createContext, useContext, useState, ReactNode } from "react";
// Interface que define o formato dos dados do contexto
interface ThemeContextData {
isDarkMode: boolean; // Estado que indica se o tema escuro está ativado
toggleTheme: () => void; // Função que alterna entre tema claro/escuro
}
// Interface para os props do ThemeProvider
interface ThemeProviderProps {
children: ReactNode; // Tipagem para os componentes filhos que o ThemeProvider vai envolver
}
// Cria o contexto ThemeContext com um valor inicial vazio, tipado com ThemeContextData
export const ThemeContext = createContext<ThemeContextData>({} as ThemeContextData);
// Componente ThemeProvider que será usado para fornecer o contexto aos componentes filhos
export function ThemeProvider({ children }: ThemeProviderProps) {
// Define o estado para controlar o modo do tema (escuro/claro)
const [isDarkMode, setIsDarkMode] = useState(false); // Inicia o tema como claro (false)
// Função para alternar entre o modo escuro e claro
const toggleTheme = () => {
setIsDarkMode(!isDarkMode); // Inverte o valor atual de isDarkMode
};
// Retorna o provedor do contexto com os valores de isDarkMode e toggleTheme
return (
<ThemeContext.Provider value={{ isDarkMode, toggleTheme }}>
{children} {/* Renderiza os componentes filhos envolvidos */}
</ThemeContext.Provider>
);
}
// Hook personalizado para consumir o contexto de tema de forma simplificada
export function useTheme(): ThemeContextData {
// Usa o hook useContext para acessar o contexto ThemeContext
const context = useContext(ThemeContext);
// Lança um erro se o hook for usado fora do ThemeProvider
if (!context) {
throw new Error('useTheme deve ser usado dentro de um ThemeProvider');
}
// Retorna o contexto para ser usado nos componentes que chamarem o hook
return context;
}
```
### Página Home
Página Home utilizando o booleano do modo escuro para trocar a estilização do tailwind
```typescript
// app/home.tsx
import { View, Text, TouchableOpacity } from 'react-native';
import { styled } from 'nativewind';
import { useAuth } from '../../context/authContext';
import { useTheme } from '../../context/themeContext';
import { router } from 'expo-router';
const StyledView = styled(View);
const StyledText = styled(Text);
const StyledTouchableOpacity = styled(TouchableOpacity);
export default function Home() {
const { isDarkMode, toggleTheme } = useTheme();
const { user, signOut } = useAuth();
function handleLogout() {
signOut();
router.replace("/");
}
return (
<StyledView
className={`flex-1 p-5
${isDarkMode ? 'bg-gray-900' : 'bg-white'}`}
>
<StyledView
className={`p-4 rounded-lg mb-6
${isDarkMode ? 'bg-gray-800' : 'bg-gray-100'}`}
>
<StyledText
className={`text-2xl font-bold mb-4
${isDarkMode ? 'text-white' : 'text-gray-900'}`}
>
Bem vindo(a)!
</StyledText>
<StyledText
className={`text-lg mb-2
${isDarkMode ? 'text-gray-200' : 'text-gray-700'}`}
>
Nome: {user?.name}
</StyledText>
<StyledText
className={`text-lg
${isDarkMode ? 'text-gray-200' : 'text-gray-700'}`}
>
Email: {user?.email}
</StyledText>
</StyledView>
<StyledTouchableOpacity
className="w-full h-12 bg-secondary rounded-lg justify-center items-center mb-4"
onPress={toggleTheme}
>
<StyledText className={`rounded text-lg font-semibold p-3 ${isDarkMode ? 'bg-white text-gray-700' : 'bg-gray-700 text-white'}`}>
{isDarkMode ? 'Modo Claro' : 'Modo Escuro'}
</StyledText>
</StyledTouchableOpacity>
<StyledTouchableOpacity
className="w-full h-12 bg-red-600 rounded-lg justify-center items-center"
onPress={handleLogout}
>
<StyledText className="text-white text-lg font-semibold">
Sair
</StyledText>
</StyledTouchableOpacity>
<StyledTouchableOpacity
className="w-full h-12 bg-red-600 rounded-lg justify-center items-center"
onPress={() => router.push("/setting")}
>
<StyledText className="text-white text-lg font-semibold">
Configurações
</StyledText>
</StyledTouchableOpacity>
</StyledView>
);
}
```
## LanguageContext
Esse código implementa um contexto de idioma para um aplicativo React Native, permitindo mudar o idioma dinamicamente, buscar traduções, e salvar a preferência do usuário no armazenamento local para que seja persistida mesmo após fechar o aplicativo. A recomendação é o i18n porém funciona também
### Pasta locales
Pasta contendo arquivos de traduções em formato chave-valor
```typescript
// src/locales/pt.ts
export default {
auth: {
welcome: "Seja bem-vindo(a)!",
login: "Entrar",
logout: "Sair",
email: "Digite seu email",
password: "Digite sua senha",
emailRequired: "Email é obrigatório",
passwordRequired: "Senha é obrigatória",
},
home: {
title: "Página Inicial",
hello: "Olá",
profile: "Perfil",
name: "Nome",
email: "Email",
plan: "Plano",
},
settings: {
title: "Configurações",
language: "Idioma",
theme: "Tema",
darkMode: "Modo Escuro",
lightMode: "Modo Claro",
},
common: {
loading: "Carregando...",
error: "Ocorreu um erro",
success: "Sucesso!",
save: "Salvar",
cancel: "Cancelar",
confirm: "Confirmar",
},
};
```
```typescript
// src/locales/en.ts
export default {
auth: {
welcome: "Welcome!",
login: "Login",
logout: "Logout",
email: "Enter your email",
password: "Enter your password",
emailRequired: "Email is required",
passwordRequired: "Password is required",
},
home: {
title: "Home",
hello: "Hello",
profile: "Profile",
name: "Name",
email: "Email",
plan: "Plan",
},
settings: {
title: "Settings",
language: "Language",
theme: "Theme",
darkMode: "Dark Mode",
lightMode: "Light Mode",
},
common: {
loading: "Loading...",
error: "An error occurred",
success: "Success!",
save: "Save",
cancel: "Cancel",
confirm: "Confirm",
},
};
```
### LanguageContext
```typescript
import { createContext, useContext, useState, ReactNode, useEffect } from 'react';
import AsyncStorage from '@react-native-async-storage/async-storage';
import pt from '../locales/pt'; // Importa as traduções em Português
import en from '../locales/en'; // Importa as traduções em Inglês
import es from '../locales/es'; // Importa as traduções em Espanhol
// Tipo para definir o formato das traduções (usando a estrutura do arquivo de tradução pt como referência)
type Translations = typeof pt;
// Interface para os dados que o contexto vai fornecer
interface LanguageContextData {
language: string; // Idioma atual
setLanguage: (lang: string) => Promise<void>; // Função para alterar o idioma
translate: (key: string, section?: keyof Translations) => string; // Função de tradução
availableLanguages: { code: string; name: string; }[]; // Lista de idiomas disponíveis
}
// Interface para as propriedades do provider (envolve os componentes filhos)
interface LanguageProviderProps {
children: ReactNode;
}
// Definimos a chave para salvar o idioma no AsyncStorage
const LANGUAGE_STORAGE_KEY = '@MyApp:language';
// Mapa que contém todas as traduções disponíveis para os idiomas suportados
const translations = {
pt, // Português
en, // Inglês
es, // Espanhol
};
// Lista de idiomas que podem ser selecionados no app
const availableLanguages = [
{ code: 'pt', name: 'Português' }, // Idioma: Português
{ code: 'en', name: 'English' }, // Idioma: Inglês
{ code: 'es', name: 'Español' }, // Idioma: Espanhol
];
// Criação do contexto com um valor inicial vazio (para ser preenchido mais tarde)
export const LanguageContext = createContext<LanguageContextData>({} as LanguageContextData);
// Função que define o LanguageProvider, envolvendo os componentes filhos e provendo o contexto
export function LanguageProvider({ children }: LanguageProviderProps) {
// Estado inicial do idioma é "pt" (Português)
const [language, setLanguageState] = useState('pt');
// useEffect é usado para carregar o idioma salvo assim que o componente monta
useEffect(() => {
loadSavedLanguage();
}, []);
// Função que tenta carregar o idioma salvo do AsyncStorage
async function loadSavedLanguage() {
try {
const savedLanguage = await AsyncStorage.getItem(LANGUAGE_STORAGE_KEY);
if (savedLanguage) {
setLanguageState(savedLanguage); // Se encontrar o idioma, define no estado
}
} catch (error) {
console.error('Error loading language:', error); // Mostra erro no console se houver falha
}
}
// Função assíncrona para alterar o idioma e salvar no AsyncStorage
async function setLanguage(lang: string) {
try {
await AsyncStorage.setItem(LANGUAGE_STORAGE_KEY, lang); // Salva o novo idioma no armazenamento
setLanguageState(lang); // Atualiza o estado com o novo idioma
} catch (error) {
console.error('Error saving language:', error); // Exibe erro se houver problema ao salvar
}
}
// Função que retorna a tradução correspondente para uma chave de tradução
function translate(key: string, section?: keyof Translations): string {
const keys = key.split('.'); // Divide a chave em partes se usar notação de ponto (ex: "perfil.nome")
let translation: any = translations[language as keyof typeof translations]; // Obtém o objeto de tradução para o idioma atual
// Se houver uma seção definida (como 'perfil'), acessa essa parte específica das traduções
if (section) {
translation = translation[section];
}
// Percorre as chaves da tradução para chegar no valor final
for (const k of keys) {
if (translation[k] === undefined) {
console.warn(`Translation not found for key: ${key}`); // Se não encontrar, mostra um aviso e retorna a própria chave como fallback
return key;
}
translation = translation[k]; // Navega para o próximo nível da tradução
}
return translation; // Retorna a tradução final
}
// Retorna o provider do contexto, fornecendo os valores e funções para os componentes filhos
return (
<LanguageContext.Provider
value={{
language, // Idioma atual
setLanguage, // Função para mudar o idioma
translate, // Função de tradução
availableLanguages // Lista de idiomas disponíveis
}}
>
{children} {/* Renderiza os componentes filhos */}
</LanguageContext.Provider>
);
}
// Hook personalizado para facilitar o uso do contexto em outros componentes
export function useLanguage(): LanguageContextData {
const context = useContext(LanguageContext); // Usa o contexto criado
// Se o contexto não for encontrado (caso o componente não esteja dentro do provider), lança um erro
if (!context) {
throw new Error('useLanguage deve ser usado dentro de um LanguageProvider');
}
return context; // Retorna o contexto
}
```
- **Tipo e Interface**:
* `Translations` define o tipo das traduções, assumindo que a estrutura do arquivo de tradução em Português é igual para todos os idiomas.
* `LanguageContextData` define a interface para o contexto de idioma, contendo o idioma atual, a função para mudar de idioma (setLanguage), a função de tradução (t) e a lista de idiomas disponíveis.
- LanguageProvider:
* Gerencia o idioma atual usando `useState`, com valor inicial pt (Português).
* Ao montar, ele carrega o idioma previamente salvo do `AsyncStorage` usando `useEffect` e a função `loadSavedLanguage`.
* `setLanguage` é uma função assíncrona que permite trocar o idioma e salva a nova configuração no armazenamento local.
- Função `translate` para Traduções:
- A função `translate` recebe uma chave (como `"perfil.nome"`) e retorna a tradução correspondente.
- Ela navega pelos objetos de tradução até encontrar o valor correspondente à chave, tratando traduções que usam uma notação de ponto.
- **Uso do Provider**:
- O `LanguageContext.Provider` fornece o idioma atual, a função de alteração de idioma, a função de tradução e a lista de idiomas para qualquer componente filho dentro do provider.
### Página Settings
```typescript
// app/settings.tsx
import { View, Text, TouchableOpacity } from 'react-native';
import { styled } from 'nativewind';
import { useLanguage } from '../../context/languageContext';
const StyledView = styled(View);
const StyledText = styled(Text);
const StyledTouchableOpacity = styled(TouchableOpacity);
export default function Settings() {
const { translate, language, setLanguage, availableLanguages } = useLanguage();
return (
<StyledView className="flex-1 p-5 bg-white">
<StyledText className="text-2xl font-bold mb-6">
{translate('settings.title')}
</StyledText>
<StyledText className="text-lg mb-4">
{translate('settings.language')}
</StyledText>
{availableLanguages.map((lang) => (
<StyledTouchableOpacity
key={lang.code}
className={`p-4 mb-2 rounded-lg border ${
language === lang.code ? 'bg-primary' : 'bg-white'
}`}
onPress={() => setLanguage(lang.code)}
>
<StyledText
className={`text-lg ${
language === lang.code ? 'text-white' : 'text-gray-900'
}`}
>
{lang.name}
</StyledText>
</StyledTouchableOpacity>
))}
</StyledView>
);
}
```
Este código exibe uma tela de configurações de idioma, permitindo que o usuário alterne entre diferentes idiomas suportados. A interface se adapta de forma dinâmica com base no idioma atual, usando estilos condicionais para destacar o idioma selecionado.