Cómo implementar un menú lateral (Drawer Navigator) en React Native

En este artículo, te explico paso a paso cómo agregar un menú lateral (Drawer Navigator) a tu aplicación utilizando @react-navigation/drawer y otras dependencias necesarias. También detallo por qué es importante incluir <Drawer.Screen name="StackNavigator" component={StackNavigator} /> en el menú para que la navegación funcione correctamente.


Pasos para configurar el Drawer Navigator

1. Instalar la dependencia de Drawer Navigator

El paquete @react-navigation/drawer es necesario para implementar un menú lateral en tu aplicación.

Ejecuta el siguiente comando para instalarlo:

npm install @react-navigation/drawer

2. Instalar las dependencias requeridas

El Drawer Navigator depende de otras bibliotecas como react-native-gesture-handler y react-native-reanimated para manejar gestos y animaciones fluidas. Instálalas con:

npm install react-native-gesture-handler react-native-reanimated

3. Configurar Babel para usar Reanimated

Reanimated requiere un plugin específico en tu archivo babel.config.js para funcionar correctamente.

Edita tu archivo babel.config.js y asegúrate de incluir el siguiente código:

module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: [
    'react-native-reanimated/plugin', // Este plugin debe estar al final de la lista
  ],
};

Una vez modificado, reinicia el servidor de tu aplicación con:

npm start --reset-cache

Nota: Puedes consultar más información sobre la configuración en la documentación oficial de Reanimated.


4. Crear el componente del Drawer Navigator

Vamos a configurar el Drawer Navigator en un nuevo archivo. Este componente será responsable de definir el menú lateral de nuestra aplicación.

  1. Ve al directorio presentation/routes.
  2. Crea un nuevo archivo llamado SideMenuNavigator.tsx.
  3. Añade el siguiente código:
import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { ProfileScreen } from '../screens/profile/ProfileScreen';
import { StackNavigator } from './StackNavigator';

const Drawer = createDrawerNavigator();

export const SideMenuNavigator = () => {
  return (
    <Drawer.Navigator>
      <Drawer.Screen name="StackNavigator" component={StackNavigator} />
      <Drawer.Screen name="Profile" component={ProfileScreen} />
    </Drawer.Navigator>
  );
};

Por qué es necesario incluir <Drawer.Screen name="StackNavigator" component={StackNavigator} />

El Drawer Navigator actúa como un contenedor para las pantallas que se mostrarán en el menú lateral. Cada <Drawer.Screen /> define una ruta que puede ser seleccionada desde el menú.

  1. Componente principal de la navegación: En este caso, StackNavigator es probablemente el componente que maneja las rutas principales de tu aplicación mediante un Stack Navigator. Al incluirlo en el Drawer, permites que los usuarios puedan acceder a las pantallas principales desde el menú lateral.

  2. Acceso a múltiples pantallas: Si omites el <Drawer.Screen /> correspondiente al StackNavigator, las rutas definidas dentro del Stack Navigator no estarán accesibles a través del menú lateral. Esto significa que no podrás navegar hacia otras pantallas de tu aplicación.

  3. Navegación jerárquica: El diseño habitual en aplicaciones con menús laterales es tener un Stack Navigator como parte de una de las opciones del Drawer. Esto permite que el Drawer maneje la navegación global, mientras que el Stack gestiona la navegación interna de pantallas específicas.

En resumen, incluir <Drawer.Screen name="StackNavigator" component={StackNavigator} /> asegura que las pantallas definidas dentro del Stack Navigator estén accesibles desde el Drawer Navigator, proporcionando una navegación fluida y jerárquica en la aplicación.


Personalizando el Drawer Navigator en React Native

En esta guía, exploraremos cómo personalizar un Drawer Navigator en React Native para crear un menú lateral más interactivo y atractivo. A través de ejemplos detallados, aprenderás cómo modificar el comportamiento del menú, quitar elementos predeterminados y diseñar un sidebar completamente personalizado.


Agregar un botón de menú en la pantalla Home

Código base:

import React, { useEffect } from 'react';
import { PrimaryButton } from '../../components/shared/PrimaryButton';
import { Pressable, View } from 'react-native';
import { DrawerActions, NavigationProp, useNavigation } from '@react-navigation/native';
import { RootStackParams } from '../../routes/StackNavigator';
import { Text } from 'react-native-paper';

export const HomeScreen = () => {
  const navigation = useNavigation<NavigationProp<RootStackParams>>();

  useEffect(() => {
    navigation.setOptions({
      headerLeft: () => (
        <Pressable
          onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
        >
          <Text>menu</Text>
        </Pressable>
      ),
    });
  }, [navigation]);

  return (
    <View>
      <PrimaryButton
        onPress={() => navigation.navigate('Products' as never)}
        text="Productos" 
      />
      <PrimaryButton
        onPress={() => navigation.navigate('Settings' as never)}
        text="Settings" 
      />
    </View>
  );
};

Explicación del código:

  1. Uso de useNavigation:

    • Este hook nos permite acceder al objeto navigation, que es esencial para manejar la navegación en React Navigation.
    • En este caso, lo tipamos con NavigationProp<RootStackParams> para garantizar que las rutas sean correctas.
  2. navigation.setOptions():

    • Dentro del useEffect, usamos setOptions para personalizar el encabezado (header) de la pantalla Home.
    • Añadimos un botón en el lado izquierdo (headerLeft) que, al presionarse, abre o cierra el menú lateral usando DrawerActions.toggleDrawer().
  3. Botón del menú lateral:

    • Se define un componente Pressable que muestra el texto menu. Al presionarlo, se dispara la acción de alternar el Drawer Navigator.
  4. Botones de navegación:

    • Utilizamos botones personalizados (PrimaryButton) para navegar a otras pantallas, como “Productos” y “Settings”.

Resultado: Ahora, al presionar el botón “menu”, se abrirá el SideMenuNavigator.


Quitar el encabezado automático del Drawer Navigator

Cuando usamos el Drawer.Navigator, se genera automáticamente un encabezado predeterminado. Para eliminarlo, podemos configurar headerShown: false en las opciones del Drawer.

Código modificado:

import React from 'react';
import { createDrawerNavigator } from '@react-navigation/drawer';
import { ProfileScreen } from '../screens/profile/ProfileScreen';
import { StackNavigator } from './StackNavigator';

const Drawer = createDrawerNavigator();

export const SideMenuNavigator = () => {
  return (
    <Drawer.Navigator
      screenOptions={{
        headerShown: false, // Oculta el encabezado automático
      }}
    >
      <Drawer.Screen name="StackNavigator" component={StackNavigator} />
      <Drawer.Screen name="Profile" component={ProfileScreen} />
    </Drawer.Navigator>
  );
};

Explicación:

  • headerShown: false:
    • Oculta el encabezado predeterminado que React Navigation crea automáticamente.
    • Esto es útil cuando deseas usar encabezados personalizados o no necesitas encabezado en absoluto.

Diseñar un Sidebar totalmente personalizado

Para modificar el diseño del menú lateral, podemos usar la propiedad drawerContent, que nos permite definir un componente personalizado para el contenido del Drawer.

Código completo del Sidebar personalizado:

import React from 'react';
import { DrawerContentComponentProps, DrawerContentScrollView, DrawerItemList, createDrawerNavigator } from '@react-navigation/drawer';
import { ProfileScreen } from '../screens/profile/ProfileScreen';
import { StackNavigator } from './StackNavigator';
import { View, useWindowDimensions } from 'react-native';

const Drawer = createDrawerNavigator();

const CustomDrawerContent = (props: DrawerContentComponentProps) => {
  return (
    <DrawerContentScrollView>
      <View style={{
        height: 200,
        backgroundColor: '#000',
        margin: 30,
        borderRadius: 50,
      }} />
      <DrawerItemList {...props} />
    </DrawerContentScrollView>
  );
};

export const SideMenuNavigator = () => {
  const dimensions = useWindowDimensions();
  return (
    <Drawer.Navigator
      drawerContent={props => <CustomDrawerContent {...props} />}
      screenOptions={{
        headerShown: false,
        drawerType: dimensions.width >= 758 ? 'permanent' : 'slide', // Cambia el tipo de menú según el tamaño de la pantalla
        drawerActiveBackgroundColor: 'pink', // Color de fondo para el item seleccionado
        drawerActiveTintColor: 'black', // Color del texto en el item seleccionado
        drawerInactiveTintColor: 'red', // Color del texto en los items no seleccionados
        drawerItemStyle: {
          borderRadius: 100, // Bordes redondeados en los items
          paddingHorizontal: 20, // Espaciado interno en los items
        },
      }}
    >
      <Drawer.Screen name="StackNavigator" component={StackNavigator} />
      <Drawer.Screen name="Profile" component={ProfileScreen} />
    </Drawer.Navigator>
  );
};

Explicación del código:

  1. Componente CustomDrawerContent:

    • Este componente define el diseño del menú lateral.
    • Incluye un encabezado visual (una vista de color negro con bordes redondeados) y los elementos del Drawer (DrawerItemList), que son las rutas configuradas.
  2. Propiedad drawerContent:

    • Permite usar un componente personalizado como contenido del menú lateral.
  3. Propiedad screenOptions:

    • drawerType:
      • Cambia el comportamiento del Drawer dependiendo del ancho de la pantalla. Por ejemplo, en pantallas grandes, se fija como “permanent” (siempre visible), mientras que en pantallas pequeñas se usa “slide”.
    • drawerActiveBackgroundColor y drawerActiveTintColor:
      • Personalizan el color de fondo y texto del elemento seleccionado.
    • drawerInactiveTintColor:
      • Define el color del texto de los elementos no seleccionados.
    • drawerItemStyle:
      • Aplica estilos personalizados a los elementos del menú, como bordes redondeados y espaciado.

Conclusión

Personalizar un Drawer Navigator en React Native es una excelente forma de mejorar la experiencia del usuario y darle un toque único a tu aplicación. Con estas técnicas, puedes:

  • Agregar botones interactivos al encabezado.
  • Eliminar elementos predeterminados que no necesitas.
  • Diseñar un menú lateral completamente personalizado, adaptado a tu estilo.

Cómo usar useSafeAreaInsets en React Native para respetar las áreas seguras

Cuando desarrollamos aplicaciones en React Native, uno de los desafíos comunes es asegurarnos de que nuestra interfaz se adapte correctamente a dispositivos modernos con notches, barras de navegación, y otras características físicas de la pantalla. Aquí es donde entra en juego useSafeAreaInsets, un hook proporcionado por la librería react-native-safe-area-context, que nos ayuda a manejar estos espacios de manera efectiva.


¿Qué es useSafeAreaInsets?

El hook useSafeAreaInsets permite acceder a los márgenes seguros (safe area insets) de un dispositivo. Este hook es especialmente útil para evitar que el contenido de la UI se superponga con áreas restringidas del dispositivo, como:

  • El notch (la pestaña superior en algunos dispositivos iOS).
  • La barra de estado (en iOS y Android).
  • La barra de navegación o gestos (en la parte inferior).

El hook devuelve un objeto con las siguientes propiedades:

  • top: El espacio seguro desde la parte superior.
  • bottom: El espacio seguro desde la parte inferior.
  • left: El espacio seguro desde el lado izquierdo.
  • right: El espacio seguro desde el lado derecho.

¿Por qué es importante?

En Android, los valores suelen ser 0 porque muchos dispositivos no tienen notches o configuraciones similares. Sin embargo, en iOS, dispositivos como los iPhones con notches (iPhone X y más nuevos) pueden devolver valores como 59 o más para la propiedad top, dependiendo del modelo.

Si ignoramos estos valores, nuestra UI podría verse cortada o solapada con elementos del sistema, lo que generaría una mala experiencia para el usuario.


Ejemplo práctico

Supongamos que tenemos una pantalla llamada ProfileScreen y queremos asegurarnos de que su contenido respete el área segura del dispositivo. Aquí está el código completo que lo implementa:

import React from 'react';
import { Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { PrimaryButton } from '../../components/shared/PrimaryButton';
import { DrawerActions, useNavigation } from '@react-navigation/native';

export const ProfileScreen = () => {
  // Obtenemos los márgenes seguros
  const { top } = useSafeAreaInsets();
  const navigation = useNavigation();

  return (
    <View
      style={{
        flex: 1, // Para ocupar toda la pantalla
        paddingHorizontal: 20, // Espaciado lateral
        marginTop: top, // Respetar el margen seguro superior
      }}
    >
      <Text style={{ marginBottom: 10 }}>ProfileScreen</Text>

      <PrimaryButton
        onPress={() => navigation.dispatch(DrawerActions.toggleDrawer())}
        label="Abrir menú"
      />
    </View>
  );
};

Desglose del código

  1. Importación del hook:

    import { useSafeAreaInsets } from 'react-native-safe-area-context';
    

    Esto nos permite acceder a los márgenes seguros del dispositivo.

  2. Uso del hook:

    const { top } = useSafeAreaInsets();
    

    Aquí extraemos la propiedad top del objeto devuelto por el hook. Este valor corresponde al margen seguro superior del dispositivo.

  3. Estilo dinámico con el margen seguro:

    <View
      style={{
        flex: 1,
        paddingHorizontal: 20,
        marginTop: top,
      }}
    >
    

    Utilizamos el valor de top para establecer el margen superior dinámicamente, asegurándonos de que el contenido de nuestra pantalla no se superponga con el notch o la barra de estado.

  4. Uso del botón: El botón PrimaryButton en este ejemplo utiliza navigation.dispatch(DrawerActions.toggleDrawer()) para abrir el menú lateral.


Diferencias entre Android e iOS

  • Android: La mayoría de los dispositivos no tienen un notch o áreas seguras especiales, por lo que top suele ser 0.

  • iOS: Dispositivos con notches o bordes curvos (como el iPhone X en adelante) devolverán valores más altos para top. Por ejemplo, en un iPhone X, este valor puede ser 59, dependiendo del modelo.


Ventajas de usar useSafeAreaInsets

  1. Adaptabilidad: Funciona automáticamente en dispositivos con diferentes configuraciones de pantalla.
  2. Compatibilidad multiplataforma: Respeta tanto Android como iOS sin necesidad de configuraciones específicas.
  3. Mejor experiencia de usuario: Asegura que los elementos de la UI no se solapen con partes del sistema, mejorando la presentación de la app.

Conclusión

El hook useSafeAreaInsets es una herramienta imprescindible para garantizar que tu aplicación React Native sea amigable en dispositivos modernos. Si estás desarrollando una app con elementos que necesitan posicionarse cerca de los bordes de la pantalla, ¡asegúrate de utilizar este hook!

Con este enfoque, puedes crear experiencias visualmente atractivas y funcionales, sin importar el dispositivo o el sistema operativo. 😊