Navegação em React Native com boas práticas usando React Navigation

Capítulo 5

Tempo estimado de leitura: 11 minutos

+ Exercício

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

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

Continue em nosso aplicativo e ...
  • Ouça o áudio com a tela desligada
  • Ganhe Certificado após a conclusão
  • + de 5000 cursos para você explorar!
ou continue lendo abaixo...
Download App

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 isAuthenticated e 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

TemaRecomendação
OrganizaçãoCentralize navegadores em src/navigation e mantenha screens em src/screens.
TipagemCrie *ParamList por navigator e use NativeStackScreenProps/BottomTabScreenProps.
HeadersDefina padrões em screenOptions e sobrescreva por screen apenas quando necessário.
AuthDecisão de público/privado no RootNavigator; trate isBootstrapping.
Deep linkingMapeie hierarquia corretamente e valide parâmetros recebidos.
ReusoEvite duplicar screens em múltiplos navegadores sem necessidade.

Agora responda o exercício sobre o conteúdo:

Em um app com rotas públicas e privadas usando React Navigation, qual é a abordagem mais adequada para decidir entre mostrar o fluxo de autenticação ou o fluxo principal, evitando “piscar” a tela de login durante a inicialização?

Você acertou! Parabéns, agora siga para a próxima página

Você errou! Tente novamente.

A boa prática é decidir no RootNavigator entre rotas públicas e privadas, tratando isBootstrapping com uma tela neutra para evitar transições erradas (como o “piscar” do login) enquanto a sessão é restaurada.

Próximo capitúlo

Estado e ciclo de vida em React Native: hooks essenciais e comportamento de renderização

Arrow Right Icon
Capa do Ebook gratuito React Native Essencial: criando apps completos com JavaScript e boas práticas
31%

React Native Essencial: criando apps completos com JavaScript e boas práticas

Novo curso

16 páginas

Baixe o app para ganhar Certificação grátis e ouvir os cursos em background, mesmo com a tela desligada.