Ejemplo de Implementación de React Router v6

Ejemplo de Implementación de React Router v6 Créditos de imagen: Unsplash

React es una de las bibliotecas de Javascript más usadas en el front-end de las aplicaciones web. Con ella se puede desarrollar fácilmente sitios web SPA (Single Page Application), en los que la navegación a través de los diferentes apartados del sitio se realiza dentro de una sola página, llamada comúnmente layout. De esta forma la experiencia de usuario en este tipo de sitios web se asemeja a la que se tiene en cualquier aplicación de escritorio o móvil.

En React, para permitir la navegación por las páginas se suele utilizar alguna biblioteca de terceros, y entre las disponibles la más usada con diferencia es react-router. Su última versión, a fecha de la redacción de este texto, es la v6, que modifica sustancialmente la forma en que se implementa respecto a la anterior versión.

La Aplicación de ejemplo

Con el objetivo de mostrar el funcionamiento y la implementación de esta bibliotecta he desarrollado una pequeña aplicación, cuyo código describiré en este artículo. A continuación dejo los enlaces para acceder a la App.

La App también contiene un ejemplo de uso del paquete para la gestión del estado Recoil. La descripción de este paquete, que no interfiere con react-router, la puede consultar en mi artículo Ejemplo de Implementación de React Recoil.


app ejemplo react router

Instalación de la Aplicación

Si desea instalar en su equipo la aplicación para probarla, modificarla o consultar su código, lo puede hacer mediante las siguientes instrucciones.

$ git clone https://github.com/frames75/react-router-recoil-demo
$ cd react-router-recoil-demo
$ npm install
$ npm start

Instalación del paquete en un proyecto

Para poder usar react-router en otro proyecto es necesario instalarlo. Se puede hacer con la siguiente instrucción:

$ cd carpeta-del-proyecto/

$ npm install react-router-dom

El código de la Aplicación

Para la realización de la App me he basado en el tutorial oficial de react-router (en inglés). Si nunca has utilizado esta biblioteca recomiendo su lectura y realización. Si ya has trabajado con versiones anteriores o sientes curiosidad por saber cómo se implementa la última versión v6, seguir este artículo te puede ser de utilidad.

Definición del mapa de rutas

Lo primero que haremos es definir en el fichero index.js el componente <BrowserRouter>, que contendrá el resto de componentes que formarán el mapa de todas las rutas habilitadas en la aplicación.

/** index.js */

import { 
    BrowserRouter,
    Routes,
    Route 
} from 'react-router-dom';

ReactDOM.render(
  <BrowserRouter basename="/react-router-recoil-demo">
    <Routes>
      <Route path='/' element={<App />} >
        <Route  index
                element={<Home />} />
        <Route  path='todoListRecoil' 
                element={<TodoListRecoil />} />
        <Route  path='invoices' 
                element={<Invoices />} >
          <Route index
            element={
              <main>
                <p>Select an invoice</p>
              </main>
            }
          />
          <Route  path=':invoiceId' 
                  element={<Invoice />} />
        </Route>
        <Route path='*'
            element={
              <main>
                <p>There's nothing here!</p>
              </main>
            }>
        </Route>
      </Route>
    </Routes>
  </BrowserRouter>,
  document.getElementById('root')
);

Los componentes <Routes> y <Route> se utilizan para renderizar su element en el actual location. Se puede entender un <Route> como una instrucción if: si su path coincide con la actual URL, renderiza su element.

Cuando la location cambia, <Routes> busca por todos sus elementos hijos <Route> hasta encontrar una coincidencia, y la renderiza.

Los componentes <Route> pueden estar anidados para indicar una UI anidada, lo que correspondería con rutas de la URL anidadas. De este modo, a partir del código anterior podemos deducir las rutas posibles de la App:

  • / (Home)
  • /todoListRecoil
  • /invoices
  • /invoices/:invoiceId
  • /* (cualquier otra ruta renderizará: <p>There's nothing here!</p>)

Definiendo el layout

En esta App hemos utilizado el fichero App.js para definir en él la capa superior de la aplicación (comúnmente conocida como layout), dividiéndola en tres partes:

  • Encabezado (App-header)
  • Menú de navegación (App-nav)
  • Contenido (App-content)
/** App.js */

import { Outlet, Link } from 'react-router-dom';

function App() {
  return (
    <div className="App">
      <header className="App-header">
        <h2>React Router v6 & Recoil Demo</h2>
      </header>
      <div className="App-body">
        <nav className="App-nav">
          <Link to="">Home</Link>
          <Link to="invoices">Invoices</Link>
          <Link to="todoListRecoil">ToDo List</Link>
        </nav>
        <main className="App-content">
          <Outlet />
        </main>
      </div>
    </div>
  );
}
export default App;

Si nos fijamos, la estructura del componente App es similar a la del <Route path='/' element={<App />} > del fichero index.js, intercambiando en este caso las <Route> anidadas dentro de la ruta padre por componentes <Link>.

Un <Link> es un elemento que permite al usuario navegar a otra parte de la App, similar a la etiqueta <a> en HTML.

El componente <Outlet>

Este elemento es usado dentro del componente declarado en la ruta padre para renderizar sus elementos <Route> hijos. Esto permite a la interfaz anidada mostrar las rutas hijas cuando son renderizadas. Si la ruta seleccionada es la raíz, se renderizará la <Route index> hija. Si la ruta no está mapeada, se renderizará la <Route path='*'> hija.

Rutas dinámicas

La ruta que renderiza el componente <Invoices> es a la vez hija de la ruta <App>, y padre de las rutas dinámicas <Invoice>. Este componente lo definimos en el fichero invoices.js.

Para esta aplicación de ejemplo se ha decidido crear un segundo layout que contendrá otra barra de navegación y la zona para mostrar los datos de cada invoice. La barra de navegación se construye dinámicamente a partir de las invoices existentes en el fichero ./data.

/** invoices.js */

import { getInvoices } from '../data';
import { 
  Outlet,
  NavLink,
  useSearchParams,
  useLocation,
} from 'react-router-dom';

function QueryNavLink({ to, ...props}) {
  let location = useLocation();
  return <NavLink to={to + location.search} {...props} />;
}

export default function Invoices() {
  const invoices = getInvoices();
  let [searchParams, setSearchParams] = useSearchParams();

  const handleChangeSearch = (event) => {
    let filter = event.target.value;
    if (filter)
      setSearchParams({ filter });
    else
      setSearchParams({});
  }

  return (
    <main className="App-body">
      <nav className="App-nav">
        <h2>Invoices</h2>
        <label>Find: </label>
        <input
          value={searchParams.get("filter") || ""}
          onChange={event => handleChangeSearch(event)}
        />
        <br/>
        { invoices
          .filter(invoice => {
            const filter = searchParams.get("filter");
            if (!filter) return true;
            const name = invoice.name.toLowerCase();
            return name.startsWith(filter.toLowerCase());
          })
          .map( invoice => (
            <QueryNavLink
              style={({ isActive }) => {
                return { color: isActive ? 'red' : 'lightblue', 
                          display: "block", 
                          margin: "0.5rem 0" };
                }}
              to={`${invoice.number}`}
              key={invoice.number}
            >
              {invoice.name}
            </QueryNavLink>
        ))}
      </nav>
      <div className="App-content">
        <Outlet />
      </div>
    </main>
  )
}

Un <NavLink> es una especie de <Link> que sabe si la ruta que contiene es la activa en ese momento. De esta forma podemos resaltar el elemento seleccionado del menú con un estilo diferente. Para lograrlo hacemos uso de la propiedad isActive del componente.

Usando este componente montamos el menú de invoices, definiendo el parámetro to con el número de cada invoice. Cuando se pulse sobre una invoice se añadirá su número a la URL y se renderizará el componente <Invoice> hijo en el <Outlet /> del componente padre <Invoices>, tal como se definió en el mapa de rutas.

El hook useSearchParams

Este hook es usado para leer y modificar la cadena de consulta en la URL para la actual location. Al igual que useState, éste devuelve un array con dos valores: la cadena de consulta y una función para actualizarla.

La cadena de consulta es lo que sigue al signo ? en una URL. P.e. en https://frames75.github.io/react-router-recoil-demo/invoices/1997?filter=sa será filter=sa.

En nuestra App usamos la cadena de consulta, guardada en la variable searchParams, para filtrar las invoices buscadas en la caja de texto. Posteriormente se rerenderiza el menú de invoices con las que cumplen con el filtro.

El hook useLocation

Este hook devuelve el actual objeto location. Puede ser útil si se desea realizar alguna acción depués del cambio de la location.

En nuestra App lo usamos para que el filtro buscado se mantenga entre cambios de invoice, agregando el atributo search del objeto location a los <NavLink> del menú de invoices.

function QueryNavLink({ to, ...props}) {
  let location = useLocation();
  return <NavLink to={to + location.search} {...props} />;
}

Recursos

Artículos relacionados

Comentarios