Estrategias de revalidación
Receta
SWR ofrece múltiples estrategias de revalidación: al enfocar la ventana, por intervalo, al reconectar y manual. Configúralas de forma global o por hook para mantener los datos actualizados sin hacer over-fetching.
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((r) => r.json());
function StockPrice({ symbol }: { symbol: string }) {
const { data } = useSWR(`/api/stocks/${symbol}`, fetcher, {
refreshInterval: 5000, // Consultar cada 5 segundos
revalidateOnFocus: true, // Actualizar al enfocar la pestaña
revalidateOnReconnect: true, // Actualizar al recuperar la red
refreshWhenHidden: false, // Pausar cuando la pestaña está oculta
refreshWhenOffline: false, // Pausar cuando está sin conexión
});
return <span>${data?.price}</span>;
}Ejemplo funcional
"use client";
import useSWR, { useSWRConfig } from "swr";
import { useState } from "react";
interface Notification {
id: string;
message: string;
read: boolean;
}
const fetcher = (url: string): Promise<Notification[]> =>
fetch(url).then((r) => r.json());
export default function NotificationCenter() {
const { data: notifications, mutate } = useSWR<Notification[]>(
"/api/notifications",
fetcher,
{
refreshInterval: 30000, // Auto-refresh every 30s
revalidateOnFocus: true, // Check on tab return
}
);
const handleManualRefresh = () => {
mutate(); // Trigger manual revalidation
};
const handleMarkAllRead = async () => {
await fetch("/api/notifications/mark-read", { method: "POST" });
mutate(); // Revalidate after mutation
};
return (
<div>
<div className="flex justify-between">
<h2>Notifications ({notifications?.filter((n) => !n.read).length ?? 0} unread)</h2>
<button onClick={handleManualRefresh}>Refresh</button>
</div>
<ul>
{notifications?.map((n) => (
<li key={n.id} className={n.read ? "opacity-50" : ""}>
{n.message}
</li>
))}
</ul>
<button onClick={handleMarkAllRead}>Mark all as read</button>
</div>
);
}Profundización
Cómo funciona
- Revalidación al enfocar se dispara cuando el usuario vuelve a la pestaña del navegador. SWR escucha los eventos
visibilitychangeyfocus. - Revalidación por intervalo configura un
setIntervalque se dispara cadarefreshIntervalms. El temporizador se pausa cuando la pestaña está oculta a menos querefreshWhenHiddenseatrue. - Revalidación al reconectar se dispara cuando el navegador vuelve a estar en línea (el evento
online). - Revalidación manual se hace llamando a
mutate()(vinculado) omutate(key)(global) sin argumentos. - Todas las estrategias de revalidación se deduplican. Si varios componentes solicitan la misma clave, solo se ejecuta una obtención.
- Los datos obsoletos se muestran de inmediato mientras la revalidación se ejecuta en segundo plano.
Variaciones
Desactivar la revalidación al enfocar de forma global:
<SWRConfig value={{ revalidateOnFocus: false }}>
{children}
</SWRConfig>Polling por intervalo condicional:
const { data } = useSWR("/api/job-status", fetcher, {
// Solo consultar mientras el trabajo está en ejecución
refreshInterval: data?.status === "running" ? 1000 : 0,
});Revalidación manual con mutate global:
import { mutate } from "swr";
// Revalidar una clave específica desde cualquier lugar
mutate("/api/users");
// Revalidar todas las claves que coincidan con un filtro
mutate(
(key) => typeof key === "string" && key.startsWith("/api/"),
undefined,
{ revalidate: true }
);Revalidar solo al montar:
const { data } = useSWR("/api/config", fetcher, {
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
// Solo obtiene si no hay datos en caché
});Notas de TypeScript
- Las opciones de revalidación forman parte de
SWRConfigurationy están completamente tipadas. refreshIntervalaceptanumbero una función(latestData: Data) => numberpara intervalos dinámicos.
const { data } = useSWR<JobStatus>("/api/job", fetcher, {
refreshInterval: (data) => (data?.complete ? 0 : 2000),
});Errores comunes
- La revalidación al enfocar puede resultar agresiva para datos que cambian poco. Desactívala para datos casi estáticos como perfiles de usuario o configuración de la app.
refreshInterval: 0desactiva el polling por intervalo (es el valor predeterminado). Establecer un intervalo muy bajo (menos de 1000 ms) puede sobrecargar tu API.refreshWhenHidden: truemantiene el polling incluso cuando el usuario está en otra pestaña. Úsalo solo cuando las actualizaciones en tiempo real importen incluso en pestañas en segundo plano.mutate()sin argumentos revalida volviendo a obtener.mutate(data)reemplaza los datos de la caché sin volver a obtener. Son operaciones muy distintas.- Cuando
revalidateIfStale: false, SWR no volverá a obtener al montar si ya hay datos en caché, aunque esos datos tengan horas de antigüedad.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Revalidación al enfocar | Automática, no requiere configuración | Puede dispararse con demasiada frecuencia para datos estables |
| Polling por intervalo | Simple, predecible | Ineficiente si los datos cambian poco |
| Push con WebSocket | Tiempo real, impulsado por el servidor | Requiere infraestructura WebSocket |
| Server-Sent Events (SSE) | Push ligero, basado en HTTP | Unidireccional, conexiones de navegador limitadas |
| Revalidación manual | Control total | Requiere puntos de disparo explícitos |
FAQs
¿Qué eventos del navegador desencadenan la revalidación al enfocar?
SWR escucha los eventos visibilitychange y focus. Cuando el usuario vuelve a la pestaña del navegador, SWR dispara una revalidación en segundo plano para todas las claves activas.
¿Cómo se comporta la revalidación por intervalo cuando la pestaña está oculta?
De forma predeterminada, el temporizador de polling se pausa cuando la pestaña está oculta. Establece refreshWhenHidden: true para continuar el polling en pestañas en segundo plano, pero hazlo solo cuando las actualizaciones en tiempo real importen incluso cuando la pestaña no es visible.
¿Cuál es la diferencia entre mutate() sin argumentos y mutate(data)?
mutate()revalida volviendo a obtener desde el servidor.mutate(data)reemplaza los datos en caché directamente sin volver a obtener.- Son operaciones muy distintas con casos de uso diferentes.
¿Cómo revalido todas las claves que coincidan con un patrón?
import { mutate } from "swr";
mutate(
(key) => typeof key === "string" && key.startsWith("/api/"),
undefined,
{ revalidate: true }
);¿Qué hace revalidateIfStale: false?
SWR no volverá a obtener al montar si ya existen datos en caché, aunque esos datos tengan horas de antigüedad. Úsalo para datos que cambian poco (p. ej., configuración de la app) combinado con desactivar la revalidación al enfocar y al reconectar.
¿Cómo implemento polling por intervalo condicional que se detiene cuando un trabajo termina?
const { data } = useSWR("/api/job-status", fetcher, {
refreshInterval: data?.status === "running" ? 1000 : 0,
});Establecer refreshInterval en 0 desactiva el polling.
Error común: ¿por qué la revalidación al enfocar puede resultar demasiado agresiva para algunos datos?
Cada vez que el usuario cambia de pestaña y vuelve, SWR vuelve a obtener. Para datos estables como perfiles de usuario o configuración de la app, esto genera solicitudes innecesarias. Desactívala por hook con revalidateOnFocus: false.
Error común: ¿qué ocurre con un refreshInterval muy bajo como 100 ms?
SWR consultará tu API varias veces por segundo, lo que puede sobrecargar tu servidor y desperdiciar ancho de banda. Mantén intervalos razonables (mínimo 1000 ms para la mayoría de los casos de uso).
¿Cómo se tipifica refreshInterval en TypeScript?
Acepta number o una función (latestData: Data) => number para intervalos dinámicos:
const { data } = useSWR<JobStatus>("/api/job", fetcher, {
refreshInterval: (data) => (data?.complete ? 0 : 2000),
});¿Las revalidaciones de distintos componentes que usan la misma clave se deduplican?
Sí. Todas las estrategias de revalidación se deduplican. Si varios componentes usan la misma clave, solo se ejecuta una solicitud de red sin importar cuántos componentes desencadenen la revalidación.