Skip to content
This page has been auto-translated and may contain errors.View in English

Parámetros de ruta y ubicación

Una página de lista y una página de detalle son la dupla clásica en una app ruteada: /vans muestra todas las camionetas, y hacer clic en una debe abrir /vans/2 con los detalles de esa camioneta. Escribir una ruta separada para cada camioneta significaría tocar el router cada vez que los datos cambian. Los parámetros de ruta resuelven esto con una sola definición de ruta que coincida con todas ellas: un marcador de posición en la ruta captura cualquier valor que aparezca allí, y la página lee ese valor para traer los datos correctos. Este capítulo cubre ese patrón, más un truco relacionado: pasar información extra junto con una navegación, para que una página de detalle pueda recordar cosas como qué filtro tenía aplicado la lista.

Segmentos dinámicos y useParams

Un segmento dinámico es una sección de ruta que comienza con dos puntos. En lugar de coincidir con texto literal, coincide con cualquier cosa en esa posición y guarda el valor bajo el nombre que elegiste:

jsx
<Route path="/vans/:id" element={<VanDetail />} />

Ahora /vans/1, /vans/42 y /vans/anything todas renderizan VanDetail. Los dos puntos marcan id como una variable dentro de la ruta. Piénsalo como un parámetro de función: la definición de ruta se escribe una sola vez, y la URL proporciona el argumento. La página de lista vincula cada elemento a su propia URL con el componente Link de Routing, usualmente interpolando el id mientras mapeas sobre los datos:

jsx
{vans.map(van => (
  <Link key={van.id} to={`/vans/${van.id}`}>
    <h3>{van.name}</h3>
  </Link>
))}

Del otro lado, el hook useParams devuelve un objeto con una propiedad por cada segmento dinámico en la ruta coincidente, indexada por el nombre después de los dos puntos:

jsx
import { useParams } from 'react-router-dom'

export default function VanDetail() {
  const params = useParams()
  // en /vans/2 → { id: "2" }
  return <h1>Van #{params.id}</h1>
}

Una ruta puede contener varios segmentos, como /vans/:id/:type, y cada uno aparece como su propia clave. Un parámetro coincidente siempre es una string, incluso cuando parece un número, porque viene directo de la URL.

Traer datos para el parámetro

El parámetro usualmente es la clave para una búsqueda. La página de detalle toma el id de la URL y solicita ese registro, siguiendo el patrón basado en efectos que Fetching data cubre completamente:

jsx
import { useState, useEffect } from 'react'
import { useParams } from 'react-router-dom'

export default function VanDetail() {
  const { id } = useParams()
  const [van, setVan] = useState(null)
  const [error, setError] = useState(null)

  useEffect(() => {
    let active = true
    setVan(null)
    setError(null)
    fetch(`/api/vans/${id}`)
      .then(res => {
        if (!res.ok) throw new Error(`Request failed: ${res.status}`)
        return res.json()
      })
      .then(data => { if (active) setVan(data.vans) })
      .catch(err => { if (active) setError(err) })
    return () => { active = false }
  }, [id])

  if (error) return <h2>Disculpa, no pudimos cargar esa camioneta.</h2>
  return van ? <h1>{van.name}</h1> : <h2>Cargando...</h2>
}

El array de dependencias tiene id en lugar de estar vacío. Si la app alguna vez navega de una página de detalle a otra, digamos en una sección de "camionetas similares", React Router intercambia la URL y el parámetro sin desmontar el componente. Un array vacío saltaría la búsqueda nuevamente y dejaría la camioneta anterior en pantalla. Depender de id vuelve a ejecutar el efecto, y limpiar van al principio de la ejecución es la parte que devuelve "Cargando..." a la pantalla: sin eso, el nombre de la camioneta 2 seguiría renderizándose durante toda la duración de la solicitud de la camioneta 5.

La verificación res.ok, el catch y el flag de limpieza active son las mismas protecciones que Fetching data argumenta, y una página impulsada por parámetros las necesita más que la mayoría: el id viene de una barra de direcciones que cualquiera puede editar, y hacer clic rápidamente a través de páginas de detalle es cómo una respuesta de la camioneta que ya dejaste llega después de la que estás viendo.

Los parámetros llevan datos de la URL a una página. A veces una página también quiere saber algo sobre de dónde vino el visitante. En el proyecto VanLife del curso, la página de lista puede filtrarse hacia, digamos, solo camionetas de lujo. Haz clic en una, presiona "Volver a todas las camionetas", y un simple link de atrás te lleva a la lista sin filtrar: el filtro se pierde, lo que se vuelve molesto rápidamente una vez que varios filtros están involucrados.

Una solución sería copiar la query string a la URL de la página de detalle, y Search params cubre cuándo el estado basado en URL como ese es lo correcto: sobrevive a compartir el link con alguien más. Cuando la información es una comodidad UX para el visitante actual, React Router ofrece un canal más ligero. Link acepta una prop state, y cualquier cosa que pases viaja junto con la navegación sin aparecer en la URL:

jsx
<Link
  to={`/vans/${van.id}`}
  state={{ search: `?${searchParams.toString()}`, type: typeFilter }}
>
  <h3>{van.name}</h3>
</Link>

searchParams y typeFilter son los valores propios de useSearchParams de la página de lista, del hook que Search params cubre completamente a continuación, así que el link lleva la query string completa más el nombre de filtro actual. Cualquier valor serializable funciona, aunque un objeto con propiedades nombradas se lee mejor.

Leerlo con useLocation

La página de destino lee ese estado con el hook useLocation, que devuelve un objeto describiendo la ubicación actual: pathname, search (la propia query string de la URL actual), y state, que contiene lo que el Link entrante pasó:

jsx
import { Link, useLocation } from 'react-router-dom'

export default function VanDetail() {
  const location = useLocation()
  const search = location.state?.search || ''
  const type = location.state?.type || 'all'

  return (
    <Link to={`..${search}`} relative="path">
      &larr; Volver a {type} camionetas
    </Link>
  )
}

Los dos fallbacks son la parte importante. Alguien que llega a /vans/2 directamente, desde un marcador, un link compartido, o una URL escrita, llega con location.state establecido en null porque ningún Link los envió. Leer location.state.search entonces lanzaría un error, así que el encadenamiento opcional más un fallback mantiene la página funcionando: el link de atrás va a la lista simple y el texto dice "Volver a todas las camionetas". Con estado presente, el link restaura la query string exacta y el texto se vuelve "Volver a camionetas de lujo" o lo que sea que fue el filtro.

La prop relative="path" hace que .. suba un segmento de URL en lugar de un nivel en el árbol de rutas, lo que importa cuando la ruta de detalle tiene un padre diferente de lo que la URL sugiere. Nested routes explica esa distinción.

El camino difícil en una página impulsada por parámetros

El curso enmarca el manejo de errores como camino feliz versus camino difícil: el camino feliz asume que cada solicitud tiene éxito, el camino difícil planifica para las que no. La mecánica de carga y error en sí pertenece a Fetching data: carga derivada del resultado y el error ambos siendo aún vacíos, un estado error establecido en el catch, y returns tempranos que renderizan retroalimentación en lugar de estrellarse en datos faltantes.

Lo que este capítulo agrega es que las páginas impulsadas por parámetros multiplican los caminos difíciles. La URL es entrada editable por el usuario, así que /vans/999 está a un teclado de distancia y el servidor responderá con un 404 para un id que no existe; la verificación res.ok arriba es lo que convierte esa respuesta en estado de error y un mensaje en lugar de un objeto con forma de camioneta. Y como se cubre arriba, el estado de navegación puede estar ausente. Una página que llega a la red a través de un parámetro debe asumir que el parámetro puede estar mal, la solicitud puede fallar, y el estado puede ser null, y renderizar algo sensato en cada caso.

Nota de versión

Antes de React Router v5.1, la información de ruta llegaba solo como props: los componentes leían props.match.params y props.location. La versión 5.1 agregó los hooks useParams y useLocation, y la versión 6 eliminó la API basada en props, dejando los hooks como la única forma de acceder. Cualquier componente bajo el router puede llamarlos, sin importar cuán profundo renderice.

El estado del link es un contenedor fino sobre la History API del navegador. Cada entrada de historial puede llevar un valor state vía history.pushState, y React Router almacena tu prop state allí cuando ocurre la navegación. Ese mecanismo explica cómo se comporta: bajo BrowserRouter el valor sobrevive a un refresco de página y los botones de atrás y adelante, porque vive en la entrada de historial en sí y el navegador persiste esos. Desaparece cuando la URL viaja, porque nada de eso está codificado en la dirección: pega el link en otro navegador y location.state es null.

Eso da una regla de decisión limpia para dónde pertenecen los datos por navegación. Cualquier cosa que deba sobrevivir a compartir, marcadores, o una visita fresca va en la URL como un search param. Cualquier cosa que sea una cortesía para la sesión del visitante actual, como restaurar un filtro detrás de un botón de atrás, cabe en el estado de historial. De cualquier manera, trata location.state como no confiable y posiblemente null en cada sitio de lectura; el caso de aterrizaje directo es una certeza a escala.

Los parámetros merecen el mismo escepticismo más dos notas mecánicas. Primero, un segmento que coincidió llega como una string, mientras que un segmento opcional como /vans/:id? que no coincidió llega como undefined (una splat que no coincidió da una string vacía en su lugar), es por eso que el tipo de TypeScript es string | undefined. Una id string comparada con === contra un número falla silenciosamente, así que convierte en el límite: Number(id) === van.id.

Segundo, un cambio de parámetro re-renderiza el componente montado en lugar de remontarlo, es por eso que el efecto de búsqueda debe listar el parámetro en sus dependencias, y por qué cualquier estado derivado inicializado desde un parámetro necesita resetearse cuando cambia.

El router también coincide segmentos estáticos antes que dinámicos, así que /vans/new puede coexistir con /vans/:id: React Router clasifica la especificidad de ruta en lugar de tomar el orden de definición, lo que evita que una ruta literal sea tragada por un parámetro vecino.

JunoUna ruta, muchas páginas Un dos puntos en una ruta, como /vans/:id, crea un espacio en blanco que cualquier valor puede llenar, así que una ruta sirve una página de detalle para cada elemento en tu lista. Dentro de la página, useParams te entrega ese valor para que puedas traer los datos correctos.

Los links también pueden llevar un pequeño paquete de información extra a través de su prop state, y useLocation lo lee del otro lado. Recuerda que el paquete podría estar faltando si alguien llegó desde un marcador, así que siempre ten un fallback listo.

JunoUna ruta, muchas páginas Define path="/vans/:id" una sola vez, lee el id con useParams, y ponlo en el array de dependencias de tu efecto de búsqueda para que navegar entre páginas de detalle vuelva a traer.

Para mantener un filtro vivo detrás de un botón de atrás, pasa la query string en la prop state del Link y léela vía location.state con encadenamiento opcional y un fallback, porque los aterrizajes directos llegan con state como null.

Maneja carga, errores e ids inválidos de la misma manera que cualquier búsqueda, tratando la URL como entrada del usuario.

JunoUna ruta, muchas páginas El estado del link viaja en la entrada de historial a través de pushState, así que sobrevive a refresco y navegación hacia atrás pero nunca cruza navegadores o URLs compartidas; pon datos compartibles en search params y reserva el estado de historial para cortesías locales a la sesión.

Un parámetro coincidente es una string, y un cambio de parámetro re-renderiza sin remontar, así que los efectos deben depender de él. La coincidencia de rutas clasifica por especificidad, permitiendo que segmentos estáticos como /vans/new coexistan seguramente con /vans/:id.

Próximo: Search params, donde los filtros y órdenes de clasificación viven en la URL.