¿Por qué la navegación “profesional” importa?
En una app real, la navegación no es solo “ir de A a B”: define la arquitectura de pantallas, cómo se comparten parámetros, cómo se controlan headers, cómo se integran enlaces externos (deep links) y cómo se organizan flujos (auth, onboarding) sin duplicar lógica. En React Native, el estándar de facto es React Navigation, que ofrece patrones comunes: stack, tabs y drawer, además de herramientas para estados de foco y ciclo de vida de pantallas.
Instalación y configuración base (paso a paso)
1) Instala dependencias principales
En un proyecto React Native (CLI o Expo), instala React Navigation y dependencias nativas:
npm install @react-navigation/nativeDependencias requeridas por React Navigation:
npm install react-native-screens react-native-safe-area-contextSi usas navegación tipo stack nativa:
npm install @react-navigation/native-stackSi usas tabs:
- Escuche el audio con la pantalla apagada.
- Obtenga un certificado al finalizar.
- ¡Más de 5000 cursos para que explores!
Descargar la aplicación
npm install @react-navigation/bottom-tabsSi usas drawer:
npm install @react-navigation/drawer react-native-gesture-handler react-native-reanimated2) Envuelve tu app con NavigationContainer
Este contenedor mantiene el estado de navegación y habilita linking, temas y listeners globales.
import { NavigationContainer } from '@react-navigation/native';
export default function App() {
return (
<NavigationContainer>
{/* Tu árbol de navegadores */}
</NavigationContainer>
);
}Patrón Stack: navegación jerárquica
El stack es ideal para flujos donde “apilas” pantallas (lista → detalle → edición). Con native-stack obtienes transiciones nativas y mejor rendimiento.
Crear un Stack Navigator
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Stack = createNativeStackNavigator();
function RootStack() {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
);
}Enviar y leer parámetros de ruta
Envía parámetros con navigation.navigate:
function HomeScreen({ navigation }) {
return (
<Button
title="Ver producto"
onPress={() => navigation.navigate('Details', { productId: 'p_123' })}
/>
);
}Lee parámetros desde route.params:
function DetailsScreen({ route }) {
const { productId } = route.params;
return <Text>Producto: {productId}</Text>;
}Tip profesional: tipado de rutas (TypeScript)
En apps grandes, tipar rutas evita errores y hace autocompletado real.
type RootStackParamList = {
Home: undefined;
Details: { productId: string };
};Patrón Tabs: secciones principales
Las tabs son ideales para secciones de primer nivel (Feed, Buscar, Perfil). Normalmente cada tab contiene su propio stack interno.
Tabs con stacks anidados
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Tab = createBottomTabNavigator();
const HomeStack = createNativeStackNavigator();
const ProfileStack = createNativeStackNavigator();
function HomeStackNavigator() {
return (
<HomeStack.Navigator>
<HomeStack.Screen name="Home" component={HomeScreen} />
<HomeStack.Screen name="Details" component={DetailsScreen} />
</HomeStack.Navigator>
);
}
function ProfileStackNavigator() {
return (
<ProfileStack.Navigator>
<ProfileStack.Screen name="Profile" component={ProfileScreen} />
<ProfileStack.Screen name="Settings" component={SettingsScreen} />
</ProfileStack.Navigator>
);
}
function AppTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="HomeTab" component={HomeStackNavigator} options={{ title: 'Inicio' }} />
<Tab.Screen name="ProfileTab" component={ProfileStackNavigator} options={{ title: 'Perfil' }} />
</Tab.Navigator>
);
}Este patrón evita stacks gigantes y permite que cada sección evolucione de forma independiente.
Patrón Drawer: navegación lateral
El drawer es útil para apps con muchas secciones o navegación secundaria (panel administrativo, categorías, herramientas). A menudo envuelve tabs o stacks.
import { createDrawerNavigator } from '@react-navigation/drawer';
const Drawer = createDrawerNavigator();
function AppDrawer() {
return (
<Drawer.Navigator>
<Drawer.Screen name="Main" component={AppTabs} options={{ title: 'Principal' }} />
<Drawer.Screen name="Help" component={HelpScreen} options={{ title: 'Ayuda' }} />
</Drawer.Navigator>
);
}Control del encabezado (header) sin hacks
El header se controla con options a nivel de pantalla o navigator. Puedes definir valores estáticos o dinámicos.
Opciones estáticas
<Stack.Screen
name="Details"
component={DetailsScreen}
options={{ title: 'Detalle', headerBackTitleVisible: false }}
/>Opciones dinámicas según params
<Stack.Screen
name="Details"
component={DetailsScreen}
options={({ route }) => ({
title: `Producto ${route.params.productId}`,
})}
/>Modificar el header desde la pantalla
Útil cuando el título depende de datos cargados. Usa useLayoutEffect para evitar “parpadeos”.
import { useLayoutEffect } from 'react';
function DetailsScreen({ navigation, route }) {
useLayoutEffect(() => {
navigation.setOptions({ title: `Producto ${route.params.productId}` });
}, [navigation, route.params.productId]);
return null;
}Header personalizado
Si necesitas un header propio (branding, buscador, etc.), puedes reemplazarlo:
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
header: () => <MyCustomHeader />,
}}
/>Deep linking básico
El deep linking permite abrir pantallas desde URLs (por ejemplo, desde un email o notificación). React Navigation lo gestiona con una configuración de linking en NavigationContainer.
Configurar linking
import { NavigationContainer } from '@react-navigation/native';
const linking = {
prefixes: ['myapp://', 'https://myapp.com'],
config: {
screens: {
HomeTab: {
screens: {
Home: 'home',
Details: 'product/:productId',
},
},
ProfileTab: {
screens: {
Profile: 'me',
Settings: 'settings',
},
},
},
},
};
export default function App() {
return (
<NavigationContainer linking={linking}>
<AppDrawer />
</NavigationContainer>
);
}Con esto, myapp://product/p_123 o https://myapp.com/product/p_123 puede abrir Details con productId.
Validación de parámetros en deep links
En apps grandes, trata los params como entrada externa: valida y maneja estados vacíos.
function DetailsScreen({ route }) {
const productId = route.params?.productId;
if (!productId) return <Text>Producto no válido</Text>;
return <Text>Producto: {productId}</Text>;
}Organización por feature para escalar la navegación
Cuando la app crece, el error típico es tener un único archivo con todos los navegadores y pantallas. Una estructura por feature mantiene el código modular y reduce conflictos en equipo.
Estructura recomendada
src/
app/
navigation/
RootNavigator.tsx
linking.ts
types.ts
features/
auth/
navigation/
AuthNavigator.tsx
screens/
LoginScreen.tsx
RegisterScreen.tsx
hooks/
useAuth.ts
onboarding/
navigation/
OnboardingNavigator.tsx
screens/
WelcomeScreen.tsx
PermissionsScreen.tsx
home/
navigation/
HomeNavigator.tsx
screens/
HomeScreen.tsx
DetailsScreen.tsxRegla práctica
- Cada feature expone su propio navigator (Stack o el que aplique).
RootNavigatorcompone features (Auth, Onboarding, App).- Los tipos de rutas se centralizan o se componen por feature para evitar duplicación.
Composición en RootNavigator
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { AuthNavigator } from '@/features/auth/navigation/AuthNavigator';
import { OnboardingNavigator } from '@/features/onboarding/navigation/OnboardingNavigator';
import { AppDrawer } from '@/app/navigation/AppDrawer';
const Root = createNativeStackNavigator();
export function RootNavigator({ isSignedIn, needsOnboarding }) {
return (
<Root.Navigator screenOptions={{ headerShown: false }}>
{!isSignedIn ? (
<Root.Screen name="Auth" component={AuthNavigator} />
) : needsOnboarding ? (
<Root.Screen name="Onboarding" component={OnboardingNavigator} />
) : (
<Root.Screen name="App" component={AppDrawer} />
)}
</Root.Navigator>
);
}Este patrón evita duplicar pantallas en múltiples stacks y mantiene un único punto de decisión.
Manejo de estado de navegación: focus y ciclo de vida de pantallas
En navegación, una pantalla puede estar montada pero no visible (por ejemplo, en tabs). Por eso es clave reaccionar al focus y a eventos del navigator.
Detectar si una pantalla está enfocada
import { useIsFocused } from '@react-navigation/native';
function ProfileScreen() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'En foco' : 'En background'}</Text>;
}Ejecutar efectos al entrar en foco (sin duplicar lógica)
Ideal para refrescar datos cuando el usuario vuelve a la pantalla.
import { useFocusEffect } from '@react-navigation/native';
import { useCallback } from 'react';
function HomeScreen() {
useFocusEffect(
useCallback(() => {
// Se ejecuta al entrar en foco
// Ej: refetch()
return () => {
// Se ejecuta al perder el foco
};
}, [])
);
return null;
}Escuchar eventos del navigator
Útil para analítica o para cancelar tareas al navegar.
function DetailsScreen({ navigation }) {
useEffect(() => {
const unsub = navigation.addListener('beforeRemove', (e) => {
// e.g. confirmar salida si hay cambios sin guardar
// e.preventDefault();
});
return unsub;
}, [navigation]);
return null;
}Evitar “fetch” duplicado en tabs
- Si la pantalla permanece montada, usa
useFocusEffectpara refrescar solo al volver. - Si quieres desmontar al salir (menos común), usa
unmountOnBlur: trueen la screen/tab, sabiendo que perderás estado local.
<Tab.Screen
name="HomeTab"
component={HomeStackNavigator}
options={{ unmountOnBlur: false }}
/>Flujos típicos sin duplicar lógica: autenticación y onboarding
Un error común es duplicar pantallas (por ejemplo, tener Home en dos stacks distintos) o duplicar reglas (validar sesión en cada pantalla). La solución es centralizar la decisión de flujo en un Root Navigator y mantener estados globales (auth/onboarding) fuera de la navegación.
Modelo de estados para decidir el flujo
| Estado | Qué muestra |
|---|---|
| No autenticado | AuthNavigator (Login/Register) |
| Autenticado + onboarding pendiente | OnboardingNavigator |
| Autenticado + onboarding completo | App (Drawer/Tabs/Stacks) |
AuthNavigator (stack simple)
import { createNativeStackNavigator } from '@react-navigation/native-stack';
const Auth = createNativeStackNavigator();
export function AuthNavigator() {
return (
<Auth.Navigator>
<Auth.Screen name="Login" component={LoginScreen} options={{ title: 'Acceder' }} />
<Auth.Screen name="Register" component={RegisterScreen} options={{ title: 'Crear cuenta' }} />
</Auth.Navigator>
);
}OnboardingNavigator (stack con control de back)
En onboarding suele interesar controlar el botón atrás o el gesto para evitar saltos inconsistentes.
const Onb = createNativeStackNavigator();
export function OnboardingNavigator() {
return (
<Onb.Navigator>
<Onb.Screen
name="Welcome"
component={WelcomeScreen}
options={{ headerBackVisible: false, gestureEnabled: false }}
/>
<Onb.Screen name="Permissions" component={PermissionsScreen} options={{ title: 'Permisos' }} />
</Onb.Navigator>
);
}Transiciones entre flujos sin “reset” manual innecesario
Si el RootNavigator depende de isSignedIn y needsOnboarding, al cambiar esos flags se re-renderiza el árbol y el usuario cae en el flujo correcto. Esto evita llamar reset desde múltiples pantallas.
Ejemplo: al completar onboarding, actualiza el estado global needsOnboarding = false y el root mostrará App.
Cuándo usar navigation.reset
Úsalo solo si necesitas limpiar historial dentro del mismo flujo (por ejemplo, después de un “checkout” para volver a Home sin permitir back a pago).
navigation.reset({
index: 0,
routes: [{ name: 'Home' }],
});Buenas prácticas para una navegación mantenible
- Nombres consistentes: usa sufijos como
HomeTab,Root,Authpara evitar colisiones. - Params mínimos: pasa IDs, no objetos grandes; carga datos en la pantalla destino.
- Headers por defecto: define
screenOptionsa nivel de navigator y sobrescribe por pantalla. - Separación por feature: cada feature controla sus pantallas y su navigator; el root solo compone.
- Focus-aware: refresca datos con
useFocusEffecten lugar de depender solo deuseEffect. - Deep links: valida params y define rutas estables; evita cambiar paths sin estrategia de compatibilidad.