Saltar al contenido

Transiciones en navegación, pestañas y filtros

Dónde viven las transiciones en una aplicación real: el router de Solid envuelve cada navegación en una transición por defecto, de modo que la página vieja permanece visible mientras los datos de la ruta nueva se cargan, y useIsRouting expone el pending de esa navegación para una barra de progreso global. Del lado del estado local, los cambios de pestaña o de filtro que alimentan la fuente de un createResource deben envolverse en startTransition para heredar el mismo comportamiento sin parpadeo. Cómo encajan createAsync y las funciones de precarga que calientan la caché para que la transición sea instantánea.

El router ya usa transiciones

Cuando navegas con @solidjs/router, el cambio de ruta ocurre dentro de una transición de forma automática. Si la ruta destino lee datos que aún se están buscando —con createAsync, con una función load, o con cualquier recurso—, el router mantiene la ruta actual en pantalla hasta que esos datos resuelven, y entonces conmuta de golpe. No tienes que envolver nada: la navegación es la transición. Esto explica por qué, en una app con router bien montada, pasar de una página a otra no parpadea aunque cada página cargue lo suyo.

TSX
import { createAsync, useParams } from "@solidjs/router";function Articulo() {  const params = useParams();  const articulo = createAsync(() => obtenerArticulo(params.id));  // Al navegar aquí, la página anterior permanece visible  // mientras esta promesa resuelve; luego se conmuta entera  return (    <Suspense fallback={<Skeleton />}>      <Vista datos={articulo()} />    </Suspense>  );}

Esto reordena una intuición que muchos traen de otros frameworks, donde navegar significa desmontar la pantalla actual y montar la siguiente con su propio spinner. En Solid la pantalla actual es una verdad que se conserva hasta que la siguiente está lista, exactamente igual que en cualquier otra transición. La navegación no es un caso especial: es la aplicación más visible del mismo mecanismo que ya conoces.

useIsRouting: el pending de la navegación

Igual que useTransition te da un pending local, el router te da useIsRouting: un signal booleano que es true mientras una navegación está en vuelo. Es la pieza para una barra de progreso global —esa línea fina que corre por el borde superior de tantas aplicaciones— sin acoplarla a ninguna ruta concreta.

TSX
import { useIsRouting } from "@solidjs/router";function ProgresoNavegacion() {  const enRuta = useIsRouting();  return (    <div      class="barra-navegacion"      classList={{ activa: enRuta() }}      aria-hidden="true"    />  );}

Colócalo una sola vez, alto en el árbol, fuera del <Routes>, y anunciará cualquier navegación de la aplicación. Es la contrapartida global de los indicadores sutiles de la lección anterior: mismo principio —anotar, no reemplazar—, pero para el nivel de la ruta entera en vez del de un bloque local. Y como la página vieja sigue montada, el usuario puede seguir leyéndola —o incluso cancelar la navegación yendo a otro sitio— mientras la línea corre.

Filtros y pestañas: transiciones que tú disparas

El router te da las transiciones gratis en la navegación, pero muchos cambios de vista no cruzan rutas: refinar un catálogo con un filtro, cambiar de pestaña, reordenar una tabla. Si esos controles alteran un signal que es la fuente de un createResource, el recurso refetchea y —sin transición— el <Suspense> parpadea. La solución es la misma primitiva, disparada a mano: envuelve la escritura del signal en startTransition.

TSX
import { createResource, createSignal, startTransition, Suspense } from "solid-js";function Catalogo() {  const [filtro, setFiltro] = createSignal("todos");  const [productos] = createResource(filtro, buscarProductos);  const cambiar = (f: string) => startTransition(() => setFiltro(f));  return (    <>      <Filtros onPick={cambiar} />      <Suspense fallback={<Skeleton />}>        <Rejilla items={productos()} />      </Suspense>    </>  );}

Si además quieres atenuar la rejilla mientras el filtro nuevo carga, cambia startTransition por useTransition y usa su pending:

TSX
function CatalogoAtenuado() {  const [filtro, setFiltro] = createSignal("todos");  const [pending, start] = useTransition();  const [productos] = createResource(filtro, buscarProductos);  return (    <>      <Filtros onPick={(f) => start(() => setFiltro(f))} />      <Suspense fallback={<Skeleton />}>        <Rejilla items={productos()} classList={{ atenuado: pending() }} />      </Suspense>    </>  );}

El patrón se repite idéntico para pestañas —el signal es la pestaña activa— y para ordenación —el signal es el criterio—. La regla mental es simple: cualquier signal que sea fuente de un recurso y lo cambie el usuario merece un startTransition. Ese es el punto exacto donde una interfaz pasa de parpadear a fluir.