La paginación en React puede sentirse lenta cuando bloquea la interfaz mientras cambia de página. Con el modo concurrente y el hook useTransition, puedes hacer que esa navegación entre páginas sea fluida, asíncrona y mucho más agradable para quien usa tu aplicación de cursos.
Qué es el modo concurrente en React y por qué importa
El modo concurrente, o Concurrent Mode, permite que React maneje los renderizados de forma asíncrona. Esto cambia por completo la experiencia de paginación.
Cuando trabajas de forma síncrona, cada clic bloquea la interfaz hasta que termina el render. En cambio, con renderizado asíncrono, puedes mostrar un estado intermedio (como un loader) mientras la siguiente página se prepara. Y aquí viene lo interesante: el usuario percibe la app como más rápida, aunque el tiempo real de procesamiento sea similar.
Para lograrlo, vas a apoyarte en dos piezas clave de React: useTransition y Suspense [00:35].
¿Qué hace useTransition en React? Marca ciertas actualizaciones de estado como no urgentes, permitiendo que React las procese en segundo plano sin bloquear la interfaz.
Cómo configurar el estado inicial de la paginación
Antes de tocar transiciones, necesitas dos piezas de estado: la página actual y cuántos cursos mostrar por página.
Declaras un estado local con useState para controlar la página activa:
jsx
const [currentPage, setCurrentPage] = useState(1);
const coursesPerPage = 2;
Si tu JSON tiene seis cursos y muestras dos por página, terminas con tres páginas [01:30]. Esa división simple es la base del cálculo que harás más adelante para generar los botones.
Por qué separar coursesPerPage como constante
Mantener coursesPerPage fuera del estado tiene sentido porque no cambia con la interacción del usuario. Es una configuración fija que define el ritmo de la paginación y la cantidad total de páginas disponibles.
Cómo usar useTransition para cambios de página fluidos
El hook useTransition te entrega dos cosas: un booleano isPending que indica si hay una transición en curso, y una función startTransition para envolver las actualizaciones de estado que quieres tratar como no urgentes.
jsx
const [isPending, startTransition] = useTransition();
Con isPending puedes mostrar un mensaje de carga condicional dentro del return:
jsx
<section>
<CourseList />
{isPending && <p>Loading new page...</p>}
</section>
Mientras React prepara los nuevos cursos, el usuario ve el feedback de que algo está pasando, sin que la pantalla se congele [02:50].
¿Cuándo debo envolver una actualización en startTransition? Cuando la actualización puede tardar y no es crítica para la respuesta inmediata, como cambiar de página, filtrar listas largas o aplicar búsquedas.
Cómo generar botones de paginación dinámicos
La cantidad de botones depende del total de cursos dividido entre los cursos por página. Para eso, Array.from te permite crear un array vacío del tamaño exacto e iterar sobre él.
jsx
<div>
{Array.from({ length: courses.length / coursesPerPage }, (_, index) => (
<button
key={index}
onClick={() => {
startTransition(() => {
setCurrentPage(index + 1);
});
}}
>
{index + 1}
</button>
))}
</div>
Fíjate en tres detalles importantes de este bloque:
- El cálculo courses.length / coursesPerPage define cuántos botones renderizar.
- index + 1 convierte el índice base cero en un número de página legible.
- El onClick envuelve setCurrentPage dentro de startTransition para activar el modo concurrente.
De esa forma, cada clic le avisa a React: "esta actualización puede esperar si hay algo más urgente" [04:30].
Por qué la key debe ser única
React necesita identificar cada botón de la lista para optimizar los renders. Usar el index como key funciona bien aquí porque los botones no cambian de orden ni se eliminan dinámicamente.
Qué sigue después de la paginación básica
Con el estado, los botones y las transiciones listos, la paginación ya responde de forma fluida. El siguiente paso es traer los cursos usando Lazy Loading para cargar solo lo necesario, y aplicar memorización para evitar re-renders innecesarios cuando el estado no cambia [05:50].
Esa combinación entre useTransition, Suspense, carga diferida y memorización es la que convierte una paginación común en una experiencia realmente optimizada.
¿Ya probaste useTransition en algún proyecto? Cuéntame en los comentarios qué tipo de interacción mejoró más con el modo concurrente.