Cómo hacer un NavBar con React, desde 0

Publicado: Hace 1 día

Comenzaremos aprendiendo qué es un NavBar, dónde utilizarlo, cuáles son sus características principales y los errores más comunes al implementarlo. Esta base nos servirá para crear, paso a paso, un componente NavBar en React que cumpla con su objetivo principal: facilitar la navegación dentro de tu aplicación.

¿Qué es un NavBar?

Un NavBar o Barra de Navegación es un elemento muy utilizado en aplicaciones frontend: web, escritorio o móviles. Su objetivo principal es permitir navegar entre las secciones más importantes de una aplicación.

¿Cuándo utilizar un NavBar?

Si necesitas darle visibilidad y acceso rápido a secciones principales de tu aplicación. Algunos ejemplos comunes son:


  • Menú de navegación. Para guiar al usuario entre las secciones principales.
  • Detalle de su cuenta. Acceso al perfil o configuraciones personales.
  • Identidad de la aplicación. Mostrar el nombre o logotipo en un lugar visible.
  • Búsquedas. Incluir un buscador para localizar funcionalidades o contenido de forma rápido

Cómo hacer un NavBar básico en React

Antes de escribir cualquier línea de código debemos entender las características principales que componente un NavBar. 


NOTA: Establecer las responsabilidades de un componente desde un inicio, nos permitirá diseñar componentes más escalables y fáciles de utilizar.

Características principales

Los NavBars están compuestos de sub-componentes, donde cada uno, es responsable por una tarea específica. Los principales sub-componentes que un NavBar puede tener son:


  • Brand. Muestra el nombre o logotipo de la aplicación. Generalmente aparece en la parte izquierda.
  • Nav. Permite definir una lista de elementos de navegación.
  • Toggler. Facilita el ocultar y mostrar el menú de navegación, útil en dispositivos móviles.
  • Actions. Puede incluir elementos adicionales como formularios, botones, buscadores, iconos o cualquier otro componente que complemente la navegación.

En cuanto a su ubicación, lo más habitual es colocarlo en la parte superior, aunque también puede estar en la parte inferior o en el lateral izquierdo. Por lo general, ocupa todo el ancho disponible. La figura 1 muestra un ejemplo de un NavBar.

Figura 1. Ejemplo de un NavBar. Su objetivo principal es facilitar el acceso a las funciones más importantes de una aplicación.


Diseño

Ya teniendo más claro el contexto sobre las características principales de un NavBar es hora de saltar al diseño. Comencemos analizando la figura 2.

Figura 2. Diseño de un NavBar. Un NavBar esta compuesto de subcomponentes como: Brand, Actions, Nav y Toogler. A su vez, cada subscomponente puede contener otros elementos más específicos.


NOTA: El diseño es una de las partes más importantes en el desarrollo de software. Es donde nuestra creatividad sale a flote. Recuerda, ninguna solución es mala. El fallar nos ayuda a crear soluciones más escalables con el tiempo.
Analizando la figura 2, tenemos que el componente principal NavBar está formado por otros componentes como Brand, Actions, Nav y Toggler. A su vez, tanto Actions como Nav pueden vivirse en subcomponentes más pequeños.
Al separa el NavBar de esta manera, no solo facilita el diseño, sino que también definimos los cimientos para implementarlo en React, donde cada componente tiene una única responsabilidad, lo qué facilita su mantenimiento y reutilización.

Responsabilidades

Cada componente dentro del NavBar tiene una tarea específica:


  • NavBar. Componente principal. Coordina la ubicación y comunicación de todos sus subcomponentes.
  • Brand. Muestra el nombre o logotipo de la aplicación. Solo debe existir un por NavBar.
  • Actions. Agrupa distintas acciones dentro de la barra de navegación
  • Action. Representa una acción individual, que puede renderizar elementos como botones, formularios, iconos, etc.
  • Nav. Listado de elementos de navegación
  • NavItem. Cada elemento dentro del Nav, su función es representar un enlace a una sección de la aplicación
  • Toggler. Permite ocultar o mostrar el menú de navegación (Nav), especialmente útil en dispositivos móviles.

Implementación

Cuando llegamos a la implementación con los requerimientos claros y un diseño en mente, la implementación se vuelve más sencilla. Dicho esto, primero configuremos nuestro proyecto y una vez listo, comencemos a implementar nuestra solución.


Configuración inicial del proyecto

En este tutorial trabajaremos con Create React App, una herramienta sencilla y muy práctica para proyectos de aprendizaje o ejemplos básicos. Su mayor ventaja es que viene lista para usar, con todo lo necesario para ejecutar pruebas en nuestros componentes.


NOTA: si tu objetivo es desarrollar aplicaciones en un entorno más profesional, te sugiero revisar opciones más modernas y rápidas como Vite, Parcel.js o Rsbuild, que ofrecen una mejor experiencia para crear aplicaciones actuales.

npx create-react-app navbar-demo
cd modal-demo
npm start


Una vez ejecutados los comandos, abre un pestaña en tu navegador y escribe: 

http://localhost:3000

Implementación básica de los componentes

Una vez que tenemos nuestro ambiente listo, comencemos definiendo la estructura básica de nuestros componentes.

src/components/NavBar/NavBar.js

    1: import React from 'react';
    2: 
    3: const NavBar = () => {
    4:  return null
    5: }
    6: 
    7: export const Brand = () => {
    8:   return null
    9: }
   10: 
   11: export const Actions = () => {
   12:   return null
   13: }
   14: 
   15: export const Action = () => {
   16:   return null
   17: }
   18: 
   19: export const Nav = () => {
   20:   return null
   21: }
   22: 
   23: export const NavItem = () => {
   24:   return null
   25: }
   26: 
   27: export const Toggler = () => {
   28:   return null
   29: }
   30: 
   31: export default NavBar;


Ahora, lo siguiente que haremos es implementar la funcionalidad de cada uno de los subcomponentes para finalmente integrarlo al componente principal.


Componente Brand

El componente Brand es bastante sencillo porque su responsabilidad es mostrar un nombre o logotipo. Cómo solución, podemos hacer lo siguiente:


  1. Recibir dos props. Pasar dos props (por ejemplo, text y logo) y validarlas para asegurarnos de que tienen el formato correcto.
  2. Recibir un prop y texto como children. Permitir solo texto como children, pero agregar un prop opcional para incluir el logotipo si es necesario.
  3. Crear subcomponentes. Encapsular la lógica en un único componente (BrandNameOrLogo) o utilizar dos componentes (BrandName y BrandLogo). Esta solución es más clara que la anterior, pero puede complicar el flujo de la información entre el componente principal y los hijos.
  4. Dejarlo libre. Permitir recibir cualquier tipo de elemento como contenido (formularios, botones, imágenes, etc.). Esto ofrece mayor flexibilidad, pero deja la responsabilidad a quien consume el componente.

Cada una de las soluciones tiene sus ventajas y desventajas. Para nuestro caso, vamos a utilizar la segunda: Recibir un prop y texto como children, ya que se ajusta mejor a la responsabilidad del componente.


Veamos como luce la implementación:

src/components/NavBar/NavBar.js

. . .
    7: export const Brand = ({ children, logo }) => {
    8:   if (!children && !logo) {
    9:     throw new Error("Brand requiere al menos un texto o un logo.")
   10:   }
   11: 
   12:   if (children && typeof children !== "string") {
   13:     throw new Error("Brand solo acepta texto como children.")
   14:   }
   15: 
   16:   return (
   17:     <>
   18:       {logo && <img src={logo} alt="logo" />}
   19:       {children && <span>{children}</span>}
   20:     </>
   21:   )
   22: }
. . .


Para utilizarlo basta con hacer lo siguiente:

// Únicamente nombre
<Brand>Un salto de línea</Brand>

// Nombre o logo
<Brand logo=‘/ligadelaimagen.png’ >Un salto de línea</Brand>


// Únicamente logo
<Brand logo=‘/ligadelaimagen.png’ />


Bastante sencillo, ¿cierto? Continuemos con el siguiente componente.




Componente Nav

Este componente tiene una particularidad: se comporta como un contenedor de elementos Nav. Cada NavItem es responsable de representar un enlace.


La solución para este caso es que Nav solo permita renderizar hijos de tipo NavItem. A su vez, cada NavItem debe recibir un prop to y un children de tipo texto.


Veamos la implementación:

src/components/NavBar/NavBar.js

. . .
   32: export const Nav = ({ children }) => {
   33:   const items = Children.toArray(children)
   34: 
   35:   items.forEach((child) => {
   36:     if (!React.isValidElement(child) || child.type !== NavItem) {
   37:       throw new Error("Nav solo puede renderizar hijos de tipo <NavItem />.")
   38:     }
   39:   })
   40: 
   41:   return(
   42:     <div className='navbar-nav'>
   43:       {items}
   44:     </div>
   45:   )
   46: }
   47: 
   48: export const NavItem = ({ to, children }) => {
   49:   if (typeof to !== "string" || to.trim() === "") {
   50:     throw new Error("NavItem requiere un prop 'to' no vacío.")
   51:   }
   52:   if (typeof children !== "string") {
   53:     throw new Error("NavItem solo acepta texto como children.")
   54:   }
   55: 
   56:   return <a href={to} className='nav-item'>{children}</a>
   57: }
. . .


Utilizarlo es bastante sencillo:

<Nav>
  <NavItem to=“/enlace1”>Enlace 1</NavItem>
  <NavItem to=“/enlace2”>Enlace 2</NavItem>
  <NavItem to=“/enlace3”>Enlace 3</NavItem>
</Nav>


Simple, ¿cierto? Continuemos con el siguiente componente.

Componente Actions

Este componente es bastante similar al anterior. Sin embargo, el componente hijo Action es más flexible: permite cualquier renderizar cualquier hijo.


La solución es la siguiente:

src/components/NavBar/NavBar.js

. . .
   24: export const Actions = ({ children }) => {
   25:   const items = Children.toArray(children)
   26: 
   27:   items.forEach((child) => {
   28:     if (!React.isValidElement(child) || child.type !== Action) {
   29:       throw new Error("Actions solo puede renderizar hijos de tipo <Action />.")
   30:     }
   31:   })
   32: 
   33:   return(
   34:     <div className='navbar-actions'>
   35:       {items}
   36:     </div>
   37:   )
   38: }
   39: 
   40: export const Action = ({ children }) => {
   41:   return(
   42:     <div className='action'>
   43:       {children}
   44:     </div>
   45:   )
   46: }
. . .


Para utilizarlo:

<Actions>
  <Action>
     <button>Haz clic</button>
  </Action>
  <Action>
    <form>
    </form>
  </Action>
</Actions>


Listo, continuemos con el siguiente componente.


Componente TogglerEste componente sirve como un switch para ocultar o mostrar el componente Nav. ¿Quien debería de encargarse de esa comunicación? Así es, el componente principal NavBar. Entonces, ¿cómo debería verse este componente? Veamos la figura 3.



Figura 3. Ejemplo del componente Toggler. El componente Toggler se representa con un ícono con tres líneas horizontales (conocido como ícono hamburguesa). Sin embargo, se puede personalizar según las necesidades de la aplicación.


Como vemos en la figura 3, el Toggler esta representado por un ícono de tipo hamburguesa: ícono con tres líneas horizontales. Sin embargo, puede adaptarse a nuestras necesidades.

Agreguemos la lógica:

src/components/NavBar/NavBar.js

. . .
   75: export const Toggler = ({icon = '☰' }) => {
   76:   const [open, setOpen] = useState(false)
   77: 
   78:   const handleClick = () => {
   79:     const next = !open
   80:     setOpen(next)
   81:   }
   82: 
   83:   return (
   84:     <button type="button" className="navbar-toggler" onClick={handleClick}>
   85:       {icon}
   86:     </button>
   87:   )
   88: }
. . .


Listo.

Por último, integremos todos los componentes en el NavBar.


Componente NavBar

Lo primero es validar que el componente NavBar reciba únicamente elementos de tipo Brand, Actions, Nav y Toggler.


Veamos la solución:

src/components/NavBar/NavBar.js

. . .
    3: export const NavBar = ({ children }) => {
    4:   let brand = null
    5:   let actions = null
    6:   let nav = null
    7:   let toggler = null
    8: 
    9:   const items = Children.toArray(children)
   10: 
   11:   items.forEach((child) => {
   12:     if (!React.isValidElement(child)) {
   13:       throw new Error("NavBar solo puede renderizar componentes válidos.")
   14:     }
   15: 
   16:     switch (child.type) {
   17:       case Brand:
   18:         brand = child
   19:         break
   20:       case Actions:
   21:         actions = child
   22:         break
   23:       case Nav:
   24:         nav = child
   25:         break
   26:       case Toggler:
   27:         toggler = child
   28:         break
   29:       default:
   30:         throw new Error(
   31:           "NavBar solo puede renderizar hijos de tipo <Brand />, <Actions />, <Nav /> o <Toggler />."
   32:         )
   33:     }
   34:   })
   35: 
   36:   return (
   37:     <nav className="navbar">
   38:       {brand}
   39:       {nav}
   40:       {actions}
   41:       {toggler}
   42:     </nav>
   43:   )
   44: }
. . .


De esta forma, garantizamos la ubicación de los elementos y evitamos que quien consuma el componente tenga que preocuparse por ello.


Por último, agreguemos la lógica para que el toggler oculte o muestre el componente nav:

src/components/NavBar/NavBar.js

. . .
    3: export const NavBar = ({ children }) => {
. . .
   36:   // Si no hay Toggler, por defecto el Nav está visible
   37:   const initialOpen = toggler ? !!toggler.props?.initialOpen : true
   38:   const [isOpen, setIsOpen] = useState(initialOpen)
   39: 
   40:   // Encadena el onChange del Toggler hijo con el control del NavBar
   41:   const handleToggle = (next) => {
   42:     setIsOpen(next)
   43:     if (toggler?.props?.onChange) toggler.props.onChange(next)
   44:   }
   45: 
   46:   // Inyecta el onChange al Toggler (y respeta su initialOpen)
   47:   const togglerWithHandlers =
   48:     toggler &&
   49:     cloneElement(toggler, {
   50:       onChange: handleToggle,
   51:       initialOpen: initialOpen,
   52:     })
   53: 
   54:   return (
   55:     <nav className="navbar">
   56:       {brand}
   57:       {isOpen && nav}
   58:       {actions}
   59:       {togglerWithHandlers}
   60:     </nav>
   61:   )
   62: }
. . .
  132: export const Toggler = ({initialOpen, onChange, icon = '☰' }) => {
  133:   const [open, setOpen] = useState(initialOpen)
  134: 
  135:   const handleClick = () => {
  136:     const next = !open
  137:     setOpen(next)
  138:     onChange(next)
  139:   }
  140: 
  141:   return (
  142:     <button type="button" className="navbar-toggler" onClick={handleClick}>
  143:       {icon}
  144:     </button>
  145:   )
  146: }
. . .


De manera general, lo que hacemos es comprobar si existe un Toggler. En caso de ser así, lo clonamos para inicializar sus props onChange e initialOpen.


  • El prop onChange se encarga de notificar cada vez que el componente cambia de estado.
  • El prop initialOpen define el valor inicial de la propiedad open.

Listo, tenemos un componente NavBar que cumple con las características principales. 

Ahora, podemos enfocarnos en darle estilos y agregar nuevas funcionalidades.


Eso sí, no olvides agregar pruebas. Esto permitirá mantener el componente confiable y facilitar su mantenimiento.


Si quieres aprender más sobre el tema, te recomiendo mi libro: Testing en React: Guía práctica con Jest y React Testing Library, donde aprenderás todo lo necesario para probar tus componentes como un profesional.

Resultado

Para visualizar el resultado, te invito a ver la siguiente galería de imágenes 👇




Alternativas y librerías recomendadas

Seguro has escuchado la frase “No revientar la rueda”. Básicamente, se refiere a evitar construir algo desde cero cuando ya existen soluciones probadas. En el caso de un NavBar, ya existen librerías y frameworks para usarlos, con estilos modernos y en la gran mayoría, bastantes flexibles.


A continuación, veamos algunas de las alternativas más recomendadas, junto con ejemplos de como se implementan.


NOTA: Cuando necesites inspiración para desarrollar tus componentes, revisa las librerías existentes. Es muy probable que encuentres buenas soluciones.


Material UI

Una de las alternativas más robustas en el mercado desarrollada en React. Cuenta con una gran variedad de componentes, modernos y fáciles de utilizar. Pero no solo esto, es una solución pensada en proyectos grandes.


Entre sus componentes podemos encontrar el AppBar, que es un tipo de NavBar pero orientado a ser la barra principal de la aplicación en comparación con el NavBar donde puede cambiar según la pantalla donde estemos.


Su implementación es bastante sencillo, veamos un ejemplo:

import * as React from 'react';
import AppBar from '@mui/material/AppBar';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import MenuIcon from '@mui/icons-material/Menu';

export default function ButtonAppBar() {
  return (
    <Box sx={{ flexGrow: 1 }}>
      <AppBar position="static">
        <Toolbar>
          <IconButton
            size="large"
            edge="start"
            color="inherit"
            aria-label="menu"
            sx={{ mr: 2 }}
          >
            <MenuIcon />
          </IconButton>
          <Typography variant="h6" component="div" sx={{ flexGrow: 1 }}>
            News
          </Typography>
          <Button color="inherit">Login</Button>
        </Toolbar>
      </AppBar>
    </Box>
  );
}


Para más detalle, consulta la documentación de Material UI sobre NavBar (AppBar)

Bootstrap

Una de las herramientas más antiguas y populares del mercado, reconocida por su gran cantidad de componentes y excelente documentación. Estas características la han convertido en una de las alternativas más utilizadas. Si bien, no esta diseñada para React, su documentación y la gran cantidad de ejemplos hacen que su integración sea bastante sencillo.


Caso contrario a Material UI, el componente NavBar lo vas a encontrar como NavBar.


Utilizarlo es bastante sencillo, veamos como podríamos utilizarlo:

<nav class="navbar navbar-expand-lg bg-body-tertiary">
  <div class="container-fluid">
    <a class="navbar-brand" href="#">Navbar</a>
    <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
      <span class="navbar-toggler-icon"></span>
    </button>
    <div class="collapse navbar-collapse" id="navbarSupportedContent">
      <ul class="navbar-nav me-auto mb-2 mb-lg-0">
        <li class="nav-item">
          <a class="nav-link active" aria-current="page" href="#">Home</a>
        </li>
        <li class="nav-item">
          <a class="nav-link" href="#">Link</a>
        </li>
        <li class="nav-item dropdown">
          <a class="nav-link dropdown-toggle" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
            Dropdown
          </a>
          <ul class="dropdown-menu">
            <li><a class="dropdown-item" href="#">Action</a></li>
            <li><a class="dropdown-item" href="#">Another action</a></li>
            <li><hr class="dropdown-divider"></li>
            <li><a class="dropdown-item" href="#">Something else here</a></li>
          </ul>
        </li>
        <li class="nav-item">
          <a class="nav-link disabled" aria-disabled="true">Disabled</a>
        </li>
      </ul>
      <form class="d-flex" role="search">
        <input class="form-control me-2" type="search" placeholder="Search" aria-label="Search"/>
        <button class="btn btn-outline-success" type="submit">Search</button>
      </form>
    </div>
  </div>
</nav>


Para saber más sobre el componente NavBar de Bootstrap, haz clic en aquí


React Bootstrap

Como su nombre lo indica, es una implementación de Bootstrap adaptada a React. La gran ventaja es que no necesitas trabajar con clases CSS, sino que tienes todos los componentes de Bootstrap implementados como componentes React, listos para usarse.


Veamos el siguiente ejemplo:

import Container from 'react-bootstrap/Container';
import Nav from 'react-bootstrap/Nav';
import Navbar from 'react-bootstrap/Navbar';
import NavDropdown from 'react-bootstrap/NavDropdown';

function BasicExample() {
  return (
    <Navbar expand="lg" className="bg-body-tertiary">
      <Container>
        <Navbar.Brand href="#home">React-Bootstrap</Navbar.Brand>
        <Navbar.Toggle aria-controls="basic-navbar-nav" />
        <Navbar.Collapse id="basic-navbar-nav">
          <Nav className="me-auto">
            <Nav.Link href="#home">Home</Nav.Link>
            <Nav.Link href="#link">Link</Nav.Link>
            <NavDropdown title="Dropdown" id="basic-nav-dropdown">
              <NavDropdown.Item href="#action/3.1">Action</NavDropdown.Item>
              <NavDropdown.Item href="#action/3.2">
                Another action
              </NavDropdown.Item>
              <NavDropdown.Item href="#action/3.3">Something</NavDropdown.Item>
              <NavDropdown.Divider />
              <NavDropdown.Item href="#action/3.4">
                Separated link
              </NavDropdown.Item>
            </NavDropdown>
          </Nav>
        </Navbar.Collapse>
      </Container>
    </Navbar>
  );
}

export default BasicExample;


Para conocer más sobre el NavBar utilizando la librería React Bootstrap, haz clic en el siguiente enlace: https://react-bootstrap.netlify.app/docs/components/navbar/ 

Conclusión

Construir un NavBar en React desde cero no solo te ayuda a entender mejor cómo funciona la composición de componentes, también te permite crear soluciones a la medida de tu aplicación. Al separar responsabilidades en subcomponentes como Brand, Nav, Actions y Toggler, tu código se vuelve más claro, escalable y fácil de mantener.
Con esta base sólida, podrás agregar estilos, personalizar la experiencia en dispositivos móviles y extender la funcionalidad sin complicaciones.