wandres.dev
FILE ROUTING · rutas y layouts

Leer el estado de la URL: useParams, useSearchParams, useLocation

La URL es estado reactivo, y el router de Solid ofrece tres lentes para leerlo: useParams devuelve los segmentos dinámicos, useSearchParams lee y escribe la query string, y useLocation expone la dirección entera con pathname, search, hash y estado. Los tres son reactivos, así que leerlos dentro de un efecto o un memo suscribe la UI a los cambios de URL, y los botones de atrás y adelante del navegador funcionan gratis porque la barra de direcciones es la fuente de la verdad.

⏱ 16 min

Una URL no es solo un destino: es estado. El id del recurso que miras, el filtro que aplicaste, la pestaña abierta, la página de resultados; todo eso vive, o debería vivir, en la barra de direcciones. El router de Solid trata la URL como estado reactivo de primera clase y te da tres primitivos para leerla —useParams, useSearchParams y useLocation—, cada uno enfocado en una parte. Como los tres son reactivos, tu interfaz se recalcula sola cuando la URL cambia, vengan los cambios de un clic, de un formulario o del botón de atrás del navegador.

🎯 Al terminar esta lección sabrás
  • Leer los segmentos dinámicos de la ruta con useParams de forma reactiva.
  • Leer y escribir la query string con useSearchParams, entendiendo su fusión.
  • Inspeccionar la dirección completa —pathname, search, hash, state— con useLocation.
  • Comprender que tratar la URL como estado da enlaces compartibles y navegación de historial gratis.

useParams: los segmentos dinámicos

useParams devuelve un objeto reactivo con los parámetros capturados por los corchetes de la ruta. Leer params.id dentro del JSX, de un memo o de un efecto suscribe ese cálculo al parámetro: si el usuario navega de /usuarios/1 a /usuarios/2 sin salir de la misma plantilla de ruta, el componente no se remonta, pero todo lo que dependa de params.id se re-ejecuta.

import { useParams } from "@solidjs/router";
import { createResource } from "solid-js";

export default function Usuario() {
  const params = useParams();
  // La fuente reactiva es una funcion que lee params.id:
  const [usuario] = createResource(() => params.id, cargarUsuario);
  return <h1>{usuario()?.nombre}</h1>;
}

Como la ruta se reutiliza entre valores de parámetro, si necesitaras forzar un remontaje —para reiniciar estado interno— envuelves el contenido en un <Show> con keyed sobre params.id. Pero lo habitual es lo contrario: aprovechar que no se remonta para conservar la envoltura y solo recargar los datos.

useSearchParams: leer y escribir la query

La query string —lo que va tras el ?— es estado que tú controlas, no capturado por la ruta. useSearchParams devuelve una pareja al estilo de un signal: un objeto reactivo de lectura y una función de escritura. Leer params.orden reacciona a ?orden=precio; llamar al setter reescribe la URL, fusionando por defecto con los parámetros existentes. Para borrar una clave, le pasas undefined.

import { useSearchParams } from "@solidjs/router";

export default function Catalogo() {
  const [params, setParams] = useSearchParams();
  return (
    <>
      <button onClick={() => setParams({ orden: "precio" })}>Por precio</button>
      <button onClick={() => setParams({ orden: undefined })}>Quitar orden</button>
      <p>Orden actual: {params.orden ?? "ninguno"}</p>
    </>
  );
}

El setter admite opciones: { replace: true } reescribe la entrada del historial en vez de apilar una nueva, útil para filtros que no quieres que llenen el botón de atrás. Poner el filtro, la pestaña o la paginación en la query tiene una recompensa enorme: el estado se vuelve compartible —copias la URL y quien la abre ve lo mismo— y sobrevive a la recarga.

Un detalle sobre los valores: la query es texto, así que todo lo que leas de useSearchParams es un string o undefined, nunca un número ni un booleano; conviertes en el borde, igual que con cualquier entrada de usuario. Y cuando una misma clave aparece repetida en la URL, el router te la entrega como array, lo que te permite modelar selecciones múltiples sin salir de la barra de direcciones.

import { useSearchParams } from "@solidjs/router";

function Filtros() {
  const [params] = useSearchParams();
  const pagina = () => Number(params.pagina) || 1;
  const etiquetas = () => [params.etiqueta].flat().filter(Boolean);
  // ?etiqueta=a&etiqueta=b  ->  ["a", "b"]
  return <p>Página {pagina()} · {etiquetas().length} etiquetas</p>;
}

useLocation: la dirección completa

useLocation te da el objeto Location reactivo con la dirección entera descompuesta: pathname, search, hash, un query ya parseado en objeto, el state asociado a la navegación y una key. Es la lente para lo transversal: registrar analítica en cada cambio de página, calcular el enlace activo, decidir el comportamiento de scroll.

import { useLocation } from "@solidjs/router";
import { createEffect } from "solid-js";

export default function Analitica() {
  const location = useLocation();
  createEffect(() => {
    // Se re-ejecuta en cada navegacion, porque location es reactivo:
    registrarVista(location.pathname + location.search);
  });
  return null;
}

La diferencia práctica con useSearchParams es la dirección del flujo: location.query es de solo lectura, una foto parseada de la query, mientras que useSearchParams es de lectura y escritura. Usa useLocation para observar la URL entera; usa useSearchParams cuando además vas a modificarla.

Primitivo Qué lee ¿Escribe?
useParams segmentos dinámicos de la ruta no
useSearchParams la query string tras el ?
useLocation pathname, search, hash, state no
flowchart LR
URL[la url actual] --> P[useParams lee los segmentos dinamicos]
URL --> S[useSearchParams lee y escribe la query]
URL --> L[useLocation lee pathname search hash y estado]
P --> UI[la interfaz se recalcula sola]
S --> UI
L --> UI
style S fill:#89b4fa,color:#11111b
style UI fill:#a6e3a1,color:#11111b

Derivar la interfaz de la URL

Como los tres primitivos son reactivos, encajan en el grafo igual que cualquier signal: un createMemo que lee params.orden deriva la vista ordenada y se recalcula solo cuando ese parámetro cambia, venga el cambio de un clic o del botón de atrás. No sincronizas nada a mano; declaras la interfaz como función de la URL y el router se encarga del resto.

import { useSearchParams } from "@solidjs/router";
import { createMemo, For } from "solid-js";

function Lista(props) {
  const [params] = useSearchParams();
  const ordenados = createMemo(() =>
    ordenar(props.items, params.orden ?? "nombre"),
  );
  return <For each={ordenados()}>{(x) => <Fila dato={x} />}</For>;
}

El resultado es una interfaz que es, literalmente, una proyección pura de la dirección actual: dos pestañas con la misma URL muestran lo mismo, recargar no pierde nada, y el historial del navegador se vuelve el deshacer y rehacer de tu estado de vista.

🎯

useParams

Los corchetes de la ruta, ya capturados y reactivos. params.id alimenta un recurso sin remontar el componente.

🔎

useSearchParams

La query como estado editable. Filtros, orden y paginación viven aquí, compartibles y persistentes.

🧭

useLocation

La dirección entera, de solo lectura. Para analítica, enlace activo y decisiones de scroll.

💡
Sube el estado efímero a la URL

Muchos estados que instintivamente meterías en un createSignal local viven mejor en la query: el filtro de un catálogo, la pestaña activa, el número de página. Al ponerlos con useSearchParams los haces compartibles, marcables y resistentes a la recarga sin coste extra. El detalle fino: si ese parámetro es además la fuente de un createResource y lo cambia el usuario, envuelve la escritura en startTransition para heredar el cero parpadeo que viste en el nivel de transiciones. URL como estado y transiciones son socios naturales.

La URL es estado global reactivo que no tuviste que construir

La idea que corona este nivel es que el router te regala una pieza de arquitectura que en otros modelos tendrías que fabricar a mano: un estado global, reactivo, serializable y sincronizado con el navegador, cuya única fuente de la verdad es la barra de direcciones. Piensa en lo que eso elimina. No necesitas un store para recordar qué filtro está activo, porque el filtro es la query y la query es la URL. No necesitas cablear los botones de atrás y adelante, porque el historial del navegador ya los mueve y tus tres primitivos ya reaccionan a esos movimientos. No necesitas inventar un formato para compartir una vista concreta, porque ese formato es la propia URL, que cualquiera puede copiar, pegar o marcar. Los tres primitivos son tres lentes sobre esa única verdad: useParams mira los segmentos que la ruta capturó, useSearchParams mira y edita la query, useLocation mira la dirección completa. Y como los tres son reactivos, participan del mismo grafo de dependencias que tus signals: leerlos en un memo deriva un valor que se recalcula con la navegación, leerlos en un efecto dispara trabajo en cada cambio de página. El salto mental es dejar de pensar en la URL como un destino que provocas y empezar a pensarla como un signal que observas: escribes en ella con navegación o con setSearchParams, lees de ella con estos tres primitivos, y toda tu interfaz se convierte en una función pura de la dirección actual. Cuando lo interiorizas, buena parte del estado que creías local resulta ser, en realidad, estado de URL esperando a subir a donde pertenece.

⚔️ Convierte la URL en tu store
  1. Monta routes/usuarios/[id].tsx con useParams alimentando un createResource; navega entre ids y observa que los datos cambian sin remontar la envoltura.
  2. Añade un catálogo con useSearchParams: botones que fijan orden y otro que lo borra con undefined; recarga la página y comprueba que el orden persiste.
  3. Pasa { replace: true } al setter y verifica que cambiar el filtro no llena el botón de atrás.
  4. Coloca un componente con useLocation y un createEffect que registre pathname y search en consola en cada navegación.
  5. Envuelve el cambio de filtro en startTransition cuando alimente un recurso y comprueba que el parpadeo desaparece.