Middleware de SWR
Receta
El middleware de SWR envuelve cada llamada a useSWR, permitiéndote inyectar preocupaciones transversales como logging, cabeceras de autenticación, tiempo de petición o transformación de datos sin modificar hooks individuales.
"use client";
import { Middleware, SWRHook } from "swr";
const logger: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
const result = useSWRNext(key, fetcher, config);
if (result.error) {
console.error(`[SWR] ${key} error:`, result.error);
} else if (result.data !== undefined) {
console.log(`[SWR] ${key} loaded:`, result.data);
}
return result;
};
};Ejemplo funcional
"use client";
import useSWR, { SWRConfig, Middleware, SWRHook } from "swr";
// ---- Middleware de logging ----
const loggingMiddleware: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
const start = Date.now();
const result = useSWRNext(key, fetcher, config);
if (!result.isLoading && !result.isValidating) {
console.log(`[SWR] ${key} resolved in ${Date.now() - start}ms`);
}
return result;
};
};
// ---- Middleware de inyección de auth ----
const authMiddleware: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
const wrappedFetcher = async (...args: any[]) => {
const token = localStorage.getItem("auth_token");
if (!token) throw new Error("No autenticado");
// Si fetcher es una función, llámala; si no, usa el predeterminado
if (typeof fetcher === "function") {
return fetcher(...args);
}
// Fetch predeterminado con cabecera de auth
const url = typeof key === "string" ? key : (key as any[])[0];
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
};
return useSWRNext(key, wrappedFetcher, config);
};
};
// ---- Middleware de transformación de datos ----
const camelCaseMiddleware: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
const result = useSWRNext(key, fetcher, config);
return {
...result,
data: result.data ? toCamelCase(result.data) : result.data,
};
};
};
function toCamelCase(obj: any): any {
if (Array.isArray(obj)) return obj.map(toCamelCase);
if (obj && typeof obj === "object") {
return Object.fromEntries(
Object.entries(obj).map(([k, v]) => [
k.replace(/_([a-z])/g, (_, c) => c.toUpperCase()),
toCamelCase(v),
])
);
}
return obj;
}
// ---- Aplicar middleware ----
export default function App({ children }: { children: React.ReactNode }) {
return (
<SWRConfig
value={{
use: [loggingMiddleware, authMiddleware],
fetcher: (url: string) => fetch(url).then((r) => r.json()),
}}
>
{children}
</SWRConfig>
);
}
// ---- Uso (el middleware se ejecuta automáticamente) ----
function Dashboard() {
const { data } = useSWR("/api/dashboard");
return <div>{JSON.stringify(data)}</div>;
}Análisis profundo
Cómo funciona
- El middleware es un array de funciones pasado mediante la opción de configuración
use, ya sea globalmente enSWRConfigo por hook. - Cada middleware envuelve
useSWRNext, formando una cadena similar al middleware de Express o Redux. - El middleware se ejecuta en orden: el primero del array envuelve al segundo, que envuelve al tercero, y así sucesivamente.
- La llamada más interna es la lógica real del hook
useSWR. - El middleware puede modificar la key, reemplazar el fetcher, alterar la configuración o transformar el resultado devuelto.
- El middleware se ejecuta en cada renderizado, por lo que debe seguir las reglas de los hooks de React (sin llamadas condicionales).
Variaciones
Middleware por hook:
const { data } = useSWR("/api/special", fetcher, {
use: [specialMiddleware],
});Middleware de reintento con backoff exponencial:
const retryMiddleware: Middleware = (useSWRNext) => {
return (key, fetcher, config) => {
return useSWRNext(key, fetcher, {
...config,
onErrorRetry: (error, _key, _config, revalidate, { retryCount }) => {
if (retryCount >= 3) return;
const delay = Math.min(1000 * 2 ** retryCount, 30000);
setTimeout(() => revalidate({ retryCount }), delay);
},
});
};
};Rastreador de deduplicación de peticiones:
const dedupTracker: Middleware = (useSWRNext) => {
return (key, fetcher, config) => {
const result = useSWRNext(key, fetcher, config);
if (result.isValidating) {
performance.mark(`swr-start-${key}`);
} else {
performance.mark(`swr-end-${key}`);
performance.measure(`swr-${key}`, `swr-start-${key}`, `swr-end-${key}`);
}
return result;
};
};Notas de TypeScript
- Importa
MiddlewareySWRHookdesdeswrpara un tipado correcto. - La firma de la función middleware es
(useSWRNext: SWRHook) => SWRHook. SWRHookes(key, fetcher, config) => SWRResponse.
import { Middleware, SWRHook, SWRResponse } from "swr";
const typedMiddleware: Middleware = (useSWRNext: SWRHook) => {
return <Data, Error>(
key: string,
fetcher: ((...args: any[]) => Promise<Data>) | null,
config: any
): SWRResponse<Data, Error> => {
return useSWRNext(key, fetcher, config);
};
};Trampas comunes
- El middleware se ejecuta en cada renderizado, no solo cuando cambian los datos. Mantén el middleware ligero para evitar problemas de rendimiento.
- El orden del middleware importa. El middleware de auth debería ejecutarse normalmente antes que el de logging para que los logs reflejen peticiones autenticadas.
- Reemplazar el fetcher en el middleware significa que se ignora el fetcher original pasado a
useSWR. Asegúrate de llamar al original si pretendes envolverlo en lugar de reemplazarlo. - El middleware debe seguir las reglas de los hooks de React. No puedes llamar a
useSWRNextde forma condicional dentro del middleware. - El middleware por hook en la opción
useno reemplaza el middleware global; lo extiende. El middleware global se ejecuta primero, luego el middleware por hook. - Transformar
dataen el middleware crea una nueva referencia de objeto en cada renderizado, lo que puede provocar re-renderizados innecesarios. Memoriza si es posible.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Middleware de SWR | Componible, reutilizable, transparente | Firmas de tipos complejas, se ejecuta en cada renderizado |
| Wrapper personalizado de fetcher | Más simple, explícito | Debe aplicarse manualmente por fetcher |
| onSuccess/onError global | Integrado, sin middleware necesario | Limitado a eventos de éxito y error |
| Interceptores de Axios | Patrón familiar, a nivel de petición/respuesta | Ligado a axios, sin conocimiento de SWR |
Preguntas frecuentes
¿Qué es el middleware de SWR y cómo funciona?
El middleware es un array de funciones pasado mediante la opción de configuración use. Cada una envuelve useSWRNext, formando una cadena (como el middleware de Express). El middleware puede modificar la key, reemplazar el fetcher, alterar la configuración o transformar el resultado devuelto.
¿Cómo aplico middleware globalmente frente a por hook?
- Global: establece
use: [middleware1, middleware2]enSWRConfig. - Por hook: establece
use: [specialMiddleware]en la configuración del hook. - El middleware por hook extiende (no reemplaza) el middleware global.
¿Importa el orden del middleware?
Sí. El primer middleware del array envuelve al segundo, que envuelve al tercero, y así sucesivamente. El middleware de auth debería ejecutarse normalmente antes que el de logging para que los logs reflejen peticiones autenticadas.
Trampa: ¿Puedo llamar a useSWRNext de forma condicional dentro del middleware?
No. El middleware debe seguir las reglas de los hooks de React. No puedes llamar a useSWRNext de forma condicional: debe llamarse en cada renderizado, sin condiciones.
¿Cómo reemplazo el fetcher en el middleware sin perder el original?
Envuelve el fetcher original en lugar de reemplazarlo:
const authMiddleware: Middleware = (useSWRNext) => {
return (key, fetcher, config) => {
const wrappedFetcher = async (...args: any[]) => {
// Añade lógica de auth aquí
if (typeof fetcher === "function") {
return fetcher(...args);
}
};
return useSWRNext(key, wrappedFetcher, config);
};
};Trampa: ¿Transformar datos en el middleware provoca re-renderizados innecesarios?
Sí. Transformar data crea una nueva referencia de objeto en cada renderizado, lo que puede provocar re-renderizados en los componentes consumidores. Memoriza la transformación si es posible para mantener la estabilidad referencial.
¿Cómo tipar una función middleware en TypeScript?
import { Middleware, SWRHook } from "swr";
const myMiddleware: Middleware = (useSWRNext: SWRHook) => {
return (key, fetcher, config) => {
return useSWRNext(key, fetcher, config);
};
};La firma del middleware es (useSWRNext: SWRHook) => SWRHook.
¿Qué tipos de TypeScript debo importar para escribir middleware de SWR?
Importa Middleware, SWRHook y opcionalmente SWRResponse desde swr. Usa estos tipos en lugar de escribir la firma completa de la función manualmente, ya que las firmas son complejas.
¿Con qué frecuencia se ejecuta el middleware?
El middleware se ejecuta en cada renderizado, no solo cuando cambian los datos. Mantén el middleware ligero y evita operaciones costosas para prevenir la degradación del rendimiento.
¿Puede el middleware modificar la configuración de SWR (como añadir lógica de reintento ante errores)?
Sí. Expande la configuración existente y sobrescribe opciones específicas:
const retryMiddleware: Middleware = (useSWRNext) => {
return (key, fetcher, config) => {
return useSWRNext(key, fetcher, {
...config,
errorRetryCount: 3,
});
};
};