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