O que é navegação no React Native e por que usar o React Navigation
Em apps reais, a navegação é o mecanismo que conecta telas (screens) e define como o usuário se move entre fluxos. O React Navigation é a biblioteca mais comum para isso no ecossistema React Native, oferecendo navegadores (navigators) como Stack (empilha telas), Tabs (abas) e Drawer (menu lateral). Boas práticas envolvem: separar a configuração de navegação em um módulo próprio, tipar rotas (quando usar TypeScript), padronizar headers, organizar rotas públicas/privadas e tratar estados de carregamento na inicialização do app.
Instalação e dependências essenciais
Instale o núcleo e os navegadores que você pretende usar. Em projetos com Expo ou React Native CLI, siga as instruções oficiais para dependências nativas (por exemplo, react-native-screens, react-native-safe-area-context e, para drawer, react-native-gesture-handler).
# núcleo
npm install @react-navigation/native
# dependências comuns (verifique a doc para seu setup)
npm install react-native-screens react-native-safe-area-context
# stack
npm install @react-navigation/native-stack
# bottom tabs
npm install @react-navigation/bottom-tabs
# drawer
npm install @react-navigation/drawer
# deep linking (opcional, mas comum)
# Expo: já vem com suporte via Linking
# RN CLI: pode usar @react-native-community/async-storage para persistência, etc.Organização recomendada do módulo de navegação
Uma estrutura simples e escalável separa tipos, rotas e navegadores:
src/
navigation/
index.tsx
linking.ts
types.ts
RootNavigator.tsx
AppTabs.tsx
AppDrawer.tsx
AuthStack.tsx
AppStack.tsx
screens/
auth/
SignInScreen.tsx
home/
HomeScreen.tsx
DetailsScreen.tsx
settings/
SettingsScreen.tsx
services/
auth/
useAuth.ts
AuthProvider.tsxIdeia central: navigation não deve conhecer detalhes de UI além das screens; e as screens não devem “montar” navegadores, apenas navegar usando rotas tipadas.
Configurando o NavigationContainer
O NavigationContainer é o “container” que mantém o estado de navegação. Ele deve ficar próximo da raiz do app, geralmente em src/navigation/index.tsx ou no App.tsx.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
// src/navigation/index.tsx
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { linking } from './linking';
import { RootNavigator } from './RootNavigator';
export function AppNavigation() {
return (
<NavigationContainer linking={linking}>
<RootNavigator />
</NavigationContainer>
);
}Boas práticas do container
- Centralize deep linking e temas (se houver) no container.
- Evite criar múltiplos
NavigationContainer(a menos que você saiba exatamente o motivo). - Se precisar navegar fora de componentes React (ex.: notificações), use um navigation ref (ver seção “navegação imperativa”).
Definindo tipos de rotas (TypeScript)
Tipar rotas evita erros comuns: navegar para uma rota inexistente, esquecer parâmetros obrigatórios ou passar tipos errados. Crie um arquivo types.ts com os param lists por navigator.
// src/navigation/types.ts
export type AuthStackParamList = {
SignIn: undefined;
};
export type AppStackParamList = {
Home: undefined;
Details: { id: string; source?: 'home' | 'deeplink' };
};
export type AppTabsParamList = {
FeedTab: undefined;
SettingsTab: undefined;
};
export type RootStackParamList = {
Auth: undefined;
App: undefined;
};Quando usar TypeScript, tipar navigation e route nas screens melhora a DX e reduz bugs.
// exemplo de tipagem em uma screen do stack
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { AppStackParamList } from '../../navigation/types';
type Props = NativeStackScreenProps<AppStackParamList, 'Details'>;
export function DetailsScreen({ route, navigation }: Props) {
const { id, source } = route.params;
// ...
}Construindo uma navegação completa: Stack + Tabs + Drawer
Um padrão comum em apps: Stack para empilhar telas (ex.: Home → Details), Tabs para seções principais e Drawer para atalhos/configurações. Você não precisa usar os três sempre; use quando fizer sentido para o produto.
1) Stack do app (telas empilhadas)
// src/navigation/AppStack.tsx
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { AppStackParamList } from './types';
import { HomeScreen } from '../screens/home/HomeScreen';
import { DetailsScreen } from '../screens/home/DetailsScreen';
const Stack = createNativeStackNavigator<AppStackParamList>();
export function AppStack() {
return (
<Stack.Navigator
screenOptions={{
headerTitleAlign: 'center',
}}
>
<Stack.Screen name="Home" component={HomeScreen} options={{ title: 'Início' }} />
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ route }) => ({
title: `Detalhes #${route.params.id}`,
})}
/>
</Stack.Navigator>
);
}2) Tabs para seções principais
As tabs normalmente ficam “no topo” do app logado, e cada tab pode conter seu próprio stack (padrão recomendado). Exemplo: FeedTab usa AppStack e SettingsTab usa uma screen simples ou outro stack.
// src/navigation/AppTabs.tsx
import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import type { AppTabsParamList } from './types';
import { AppStack } from './AppStack';
import { SettingsScreen } from '../screens/settings/SettingsScreen';
const Tab = createBottomTabNavigator<AppTabsParamList>();
export function AppTabs() {
return (
<Tab.Navigator
screenOptions={{
headerShown: false,
}}
>
<Tab.Screen name="FeedTab" component={AppStack} options={{ title: 'Feed' }} />
<Tab.Screen name="SettingsTab" component={SettingsScreen} options={{ title: 'Ajustes' }} />
</Tab.Navigator>
);
}3) Drawer para atalhos e áreas secundárias
O drawer é útil quando há várias áreas que não cabem bem em tabs, ou quando você quer um menu lateral com perfil, logout, etc. Um padrão: Drawer “embrulha” as tabs.
// src/navigation/AppDrawer.tsx
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { AppTabs } from './AppTabs';
import { SettingsScreen } from '../screens/settings/SettingsScreen';
const Drawer = createDrawerNavigator();
export function AppDrawer() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Main" component={AppTabs} options={{ title: 'Principal', headerShown: false }} />
<Drawer.Screen name="Settings" component={SettingsScreen} options={{ title: 'Configurações' }} />
</Drawer.Navigator>
);
}Observação: Evite duplicar a mesma screen em múltiplos lugares (ex.: Settings em Tab e Drawer) sem necessidade. Se for o caso, prefira um único ponto de acesso.
Fluxo de autenticação: rotas públicas e privadas
Um fluxo típico separa navegação de autenticação (pública) e navegação do app (privada). A decisão do que renderizar deve depender do estado de auth (isAuthenticated) e do estado de carregamento inicial (isBootstrapping).
1) AuthStack (rotas públicas)
// src/navigation/AuthStack.tsx
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { AuthStackParamList } from './types';
import { SignInScreen } from '../screens/auth/SignInScreen';
const Stack = createNativeStackNavigator<AuthStackParamList>();
export function AuthStack() {
return (
<Stack.Navigator>
<Stack.Screen name="SignIn" component={SignInScreen} options={{ title: 'Entrar' }} />
</Stack.Navigator>
);
}2) RootNavigator decide qual fluxo mostrar
O RootNavigator é responsável por alternar entre Auth e App. Ele também é o lugar ideal para tratar o “carregamento inicial” (ex.: restaurar token do storage, validar sessão, buscar perfil).
// src/navigation/RootNavigator.tsx
import React from 'react';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import type { RootStackParamList } from './types';
import { AuthStack } from './AuthStack';
import { AppDrawer } from './AppDrawer';
import { useAuth } from '../services/auth/useAuth';
import { ActivityIndicator, View } from 'react-native';
const Stack = createNativeStackNavigator<RootStackParamList>();
function SplashScreen() {
return (
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
<ActivityIndicator />
</View>
);
}
export function RootNavigator() {
const { isAuthenticated, isBootstrapping } = useAuth();
if (isBootstrapping) {
return <SplashScreen />;
}
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
{isAuthenticated ? (
<Stack.Screen name="App" component={AppDrawer} />
) : (
<Stack.Screen name="Auth" component={AuthStack} />
)}
</Stack.Navigator>
);
}Padrões importantes para auth
- Nunca faça “redirect” de auth dentro de várias screens; centralize no RootNavigator.
- Trate isBootstrapping para evitar “piscar” a tela de login quando o app ainda está restaurando sessão.
- Se o token expirar, atualize
isAuthenticatede deixe o RootNavigator trocar o fluxo.
Passagem de parâmetros e navegação tipada
Navegando com parâmetros
// HomeScreen.tsx
import React from 'react';
import { Button, View } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { AppStackParamList } from '../../navigation/types';
type Props = NativeStackScreenProps<AppStackParamList, 'Home'>;
export function HomeScreen({ navigation }: Props) {
return (
<View>
<Button
title="Abrir detalhes"
onPress={() => navigation.navigate('Details', { id: '42', source: 'home' })}
/>
</View>
);
}Lendo parâmetros na tela de destino
// DetailsScreen.tsx
import React from 'react';
import { Text, View } from 'react-native';
import type { NativeStackScreenProps } from '@react-navigation/native-stack';
import type { AppStackParamList } from '../../navigation/types';
type Props = NativeStackScreenProps<AppStackParamList, 'Details'>;
export function DetailsScreen({ route }: Props) {
return (
<View>
<Text>ID: {route.params.id}</Text>
<Text>Origem: {route.params.source ?? 'n/a'}</Text>
</View>
);
}Headers personalizados e boas práticas
Você pode personalizar headers globalmente (no navigator) ou por screen. Prefira configurar padrões no navigator e sobrescrever apenas quando necessário.
Header global com botão de ação
// dentro de AppStack
<Stack.Navigator
screenOptions={({ navigation }) => ({
headerTitleAlign: 'center',
headerRight: () => (
<YourHeaderButton onPress={() => navigation.navigate('Details', { id: 'preview' })} />
),
})}
>
...
</Stack.Navigator>Se você estiver usando um componente customizado no header, mantenha-o pequeno e sem dependências pesadas. Para botões, use componentes acessíveis e com área de toque adequada.
Alterando opções dinamicamente
Quando o título depende de dados carregados, você pode atualizar opções após buscar os dados.
// dentro de DetailsScreen
import React, { useEffect } from 'react';
export function DetailsScreen({ navigation, route }: Props) {
useEffect(() => {
navigation.setOptions({ title: `Item ${route.params.id}` });
}, [navigation, route.params.id]);
return null;
}Deep linking básico (abrir telas via URL)
Deep linking permite abrir o app em uma tela específica a partir de uma URL, como myapp://details/42. No React Navigation, você configura um objeto linking e passa para o NavigationContainer.
1) Defina o mapeamento de rotas
// src/navigation/linking.ts
import type { LinkingOptions } from '@react-navigation/native';
import type { RootStackParamList } from './types';
export const linking: LinkingOptions<RootStackParamList> = {
prefixes: ['myapp://'],
config: {
screens: {
App: {
screens: {
// como App aponta para o Drawer, e Drawer para Tabs, você mapeia a hierarquia
Main: {
screens: {
FeedTab: {
screens: {
Home: 'home',
Details: 'details/:id',
},
},
},
},
},
},
Auth: {
screens: {
SignIn: 'signin',
},
},
},
},
};2) Lidando com parâmetros do deep link
Quando a rota é aberta via URL, o parâmetro id chega em route.params como string. Garanta validação e fallback (ex.: se o id não existir).
// dentro de DetailsScreen
const id = route.params?.id;
if (!id) {
// renderize um estado de erro ou volte
}Boas práticas para deep linking
- Não confie cegamente nos parâmetros: valide formato e permissões.
- Se a tela for privada e o usuário não estiver autenticado, direcione para Auth e, após login, retome o link (exige armazenar o destino pendente no estado de auth).
Tratando carregamento inicial (bootstrapping) do app
Ao iniciar, é comum precisar: restaurar token, validar sessão, carregar preferências e talvez buscar o perfil. Esse processo deve ser refletido no estado de navegação para evitar transições erradas.
Exemplo de contrato do hook de autenticação
// src/services/auth/useAuth.ts
export function useAuth() {
// exemplo de API do hook
return {
isAuthenticated: false,
isBootstrapping: true,
signIn: async (email: string, password: string) => {},
signOut: async () => {},
};
}O importante é o RootNavigator depender de isBootstrapping para exibir uma tela neutra (splash/loading) até que o estado real seja conhecido.
Navegação imperativa (quando você precisa navegar fora de componentes)
Alguns casos (notificações push, handlers globais, interceptadores) exigem navegar sem ter navigation via props/hook. Para isso, use um navigation ref.
// src/navigation/navigationRef.ts
import { createNavigationContainerRef } from '@react-navigation/native';
import type { RootStackParamList } from './types';
export const navigationRef = createNavigationContainerRef<RootStackParamList>();
export function navigate<T extends keyof RootStackParamList>(
name: T,
params?: RootStackParamList[T]
) {
if (navigationRef.isReady()) {
navigationRef.navigate(name, params as never);
}
}// usando no container
// src/navigation/index.tsx
import { navigationRef } from './navigationRef';
<NavigationContainer ref={navigationRef} linking={linking}>
<RootNavigator />
</NavigationContainer>Use navegação imperativa com parcimônia. Se for possível, prefira navegar a partir de screens/components com acesso ao navigation.
Checklist de boas práticas para navegação
| Tema | Recomendação |
|---|---|
| Organização | Centralize navegadores em src/navigation e mantenha screens em src/screens. |
| Tipagem | Crie *ParamList por navigator e use NativeStackScreenProps/BottomTabScreenProps. |
| Headers | Defina padrões em screenOptions e sobrescreva por screen apenas quando necessário. |
| Auth | Decisão de público/privado no RootNavigator; trate isBootstrapping. |
| Deep linking | Mapeie hierarquia corretamente e valide parâmetros recebidos. |
| Reuso | Evite duplicar screens em múltiplos navegadores sem necessidade. |