Obtención básica de datos con SWR
Receta
Usa el hook useSWR con una clave y una función fetcher para obtener, cachear y revalidar datos remotos de forma declarativa en componentes React.
"use client";
import useSWR from "swr";
const fetcher = (url: string) => fetch(url).then((res) => res.json());
function Dashboard() {
const { data, error, isLoading, isValidating } = useSWR("/api/dashboard", fetcher);
if (isLoading) return <p>Cargando...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<div>
<h1>{data.title}</h1>
{isValidating && <span>Actualizando...</span>}
</div>
);
}Ejemplo funcional
"use client";
import useSWR from "swr";
interface User {
id: number;
name: string;
email: string;
}
const fetcher = (url: string): Promise<User[]> =>
fetch(url).then((res) => {
if (!res.ok) throw new Error("La respuesta de red no fue correcta");
return res.json();
});
export default function UserList() {
const { data: users, error, isLoading } = useSWR<User[]>("/api/users", fetcher);
if (isLoading) return <div className="skeleton" />;
if (error) return <div role="alert">Error al cargar usuarios</div>;
return (
<ul>
{users?.map((user) => (
<li key={user.id}>
{user.name} — {user.email}
</li>
))}
</ul>
);
}Análisis profundo
Cómo funciona
- El primer argumento de
useSWRes la clave — una cadena única (o array, o null) que identifica la solicitud y controla la caché. - El segundo argumento es el fetcher — cualquier función async que recibe la clave y devuelve datos.
- SWR devuelve datos en caché de inmediato (obsoletos), luego revalida en segundo plano (stale-while-revalidate) y finalmente actualiza la UI con datos frescos.
isLoadingestruesolo en la primera carga cuando aún no hay datos en caché.isValidatingestruesiempre que hay una solicitud en curso, incluidas las revalidaciones en segundo plano.- SWR deduplica solicitudes idénticas realizadas dentro de la ventana
dedupingInterval(por defecto: 2000 ms).
Variaciones
Claves en array (fetcher con varios argumentos):
const fetcher = ([url, token]: [string, string]) =>
fetch(url, { headers: { Authorization: `Bearer ${token}` } }).then((r) => r.json());
const { data } = useSWR(["/api/user", token], fetcher);Usando axios:
import axios from "axios";
const fetcher = (url: string) => axios.get(url).then((res) => res.data);
const { data } = useSWR("/api/posts", fetcher);Fetcher GraphQL:
const fetcher = (query: string) =>
fetch("/api/graphql", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query }),
})
.then((res) => res.json())
.then((json) => json.data);
const { data } = useSWR("{ user { name } }", fetcher);Notas de TypeScript
- Pasa genéricos a
useSWRpara tipar el retorno:useSWR<Data, Error>(key, fetcher). - El tipo de retorno de la función fetcher debe coincidir con el genérico
Data. - El tipo de clave puede ser
string,[string, ...args]o una función que devuelva cualquiera de los dos.
import useSWR, { Fetcher } from "swr";
const fetcher: Fetcher<User[], string> = (url) =>
fetch(url).then((r) => r.json());
const { data } = useSWR<User[]>("/api/users", fetcher);
// data es User[] | undefinedErrores comunes
- La clave debe ser una referencia estable. Crear un literal de array nuevo en línea en cada renderizado (p. ej.,
useSWR([url, id], ...)) está bien porque SWR serializa las claves en array internamente, pero las claves objeto no son compatibles. - Si tu fetcher no lanza errores en respuestas HTTP, SWR tratará una respuesta 404 o 500 como datos exitosos. Comprueba siempre
res.oken tu fetcher. dataesundefineddurante la carga inicial. Maneja siempre el estado de carga o usa encadenamiento opcional.- Llamar a
useSWRen un Server Component provocará un error. Los hooks de SWR son solo del lado del cliente.
Alternativas
| Enfoque | Ventajas | Desventajas |
|---|---|---|
| useSWR | Caché automático, deduplicación, revalidación | Solo cliente, mayor tamaño del bundle |
| fetch en useEffect | Sin dependencias | Sin caché, condiciones de carrera, código repetitivo |
| React Query (TanStack) | Funciones similares, más mutaciones integradas | API más amplia |
| fetch en Server Components | Cero JS en el cliente, caché en el servidor | Sin actualizaciones en tiempo real |
Ejemplo del mundo real
De una aplicación SaaS de producción Next.js 15 / React 19 (SystemsArchitect.io).
// Ejemplo de producción: búsqueda con SWR y debounce
// Archivo: src/hooks/use-search.ts
export function useSearch() {
const [debouncedQuery, setDebouncedQuery] = useState(query);
useEffect(() => {
const timer = setTimeout(() => setDebouncedQuery(query), 300);
return () => clearTimeout(timer);
}, [query]);
const shouldFetch = debouncedQuery.length >= 3;
const apiUrl = shouldFetch
? `/api/search?q=${encodeURIComponent(debouncedQuery)}&platform=${platform}`
: null;
const { data, error, isLoading, isValidating } = useSWR<SearchResponse>(
apiUrl,
fetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
dedupingInterval: 5000,
keepPreviousData: true,
}
);
return useMemo(() => ({
results: data?.results,
isLoading: isLoading || (shouldFetch && isValidating),
}), [data, isLoading, shouldFetch, isValidating]);
}Lo que esto demuestra en producción:
- Pasar
nullcomo clave de SWR evita la obtención por completo. Esta es la forma idiomática de obtener datos de forma condicional: cuandoshouldFetchesfalse, la URL esnully SWR omite la solicitud. keepPreviousData: truemuestra los resultados de búsqueda anteriores mientras una nueva consulta está en curso, evitando un parpadeo de pantalla en blanco entre búsquedas.dedupingInterval: 5000evita solicitudes duplicadas para la misma clave en 5 segundos. Si el usuario escribe "react", lo borra y vuelve a escribir "react" en menos de 5 segundos, SWR devuelve el resultado en caché en lugar de llamar a la API.revalidateOnFocus: falseyrevalidateOnReconnect: falsedesactivan la revalidación automática en segundo plano. Para búsquedas, los resultados obsoletos son aceptables y volver a obtener al enfocar la pestaña sorprendería a los usuarios.- El
useMemoen el objeto de retorno evita que los componentes consumidores se re-rendericen cuando el estado interno del hook cambia pero los valores devueltos no han cambiado. - El estado de carga combina
isLoading(primera carga) conisValidating(obtención en segundo plano) para un indicador de carga completo.
FAQs
¿Cuál es la diferencia entre isLoading e isValidating en useSWR?
isLoadingestruesolo en la primera carga cuando no hay datos en caché.isValidatingestruesiempre que hay una solicitud en curso, incluidas las revalidaciones en segundo plano.- En revalidaciones posteriores,
isLoadingesfalseperoisValidatingestrue.
¿Puedo usar un objeto como clave para useSWR?
No. Las claves objeto no son compatibles porque SWR no puede serializarlas de forma fiable. Usa cadenas o arrays en su lugar. Las claves en array se serializan internamente, así que ["/api/users", id] funciona sin problemas.
¿Cómo deduplica SWR solicitudes idénticas?
Cuando varios componentes llaman a useSWR con la misma clave dentro de la ventana dedupingInterval (2000 ms por defecto), solo se realiza una solicitud de red. Todos los hooks reciben el mismo resultado de la promesa.
Gotcha: ¿Qué sucede si mi fetcher no comprueba res.ok antes de devolver?
SWR tratará las respuestas 404 y 500 como datos exitosos. El campo error permanecerá undefined. Valida siempre res.ok y lanza un error para respuestas que no sean 2xx.
¿Puedo usar useSWR dentro de un React Server Component?
No. useSWR es un hook de React que requiere renderizado del lado del cliente. Llamarlo en un Server Component lanzará un error. Usa fetch directamente en Server Components y pasa los datos como props.
¿Cómo uso claves en array con un fetcher de varios argumentos?
const fetcher = ([url, token]: [string, string]) =>
fetch(url, {
headers: { Authorization: `Bearer ${token}` },
}).then((r) => r.json());
const { data } = useSWR(["/api/user", token], fetcher);¿Qué hace keepPreviousData en SWR?
Preserva los datos anteriores mientras una nueva solicitud está en curso (p. ej., cuando cambia la clave). Esto evita un parpadeo de pantalla en blanco entre transiciones de clave, lo cual es especialmente útil para interfaces de búsqueda.
¿Cómo tipar correctamente useSWR con genéricos de TypeScript?
import useSWR, { Fetcher } from "swr";
const fetcher: Fetcher<User[], string> = (url) =>
fetch(url).then((r) => r.json());
const { data } = useSWR<User[]>("/api/users", fetcher);
// data es User[] | undefined¿Para qué sirve el tipo Fetcher exportado por SWR en TypeScript?
Fetcher<Data, Key> exige que el fetcher acepte el tipo de clave como entrada y devuelva Promise<Data>. Proporciona seguridad de tipos en tiempo de compilación entre la clave, el fetcher y los datos devueltos.
Gotcha: ¿Por qué data es undefined incluso después de que el componente monta?
data siempre es undefined durante la carga inicial antes de que el fetcher se resuelva. Maneja siempre el estado de carga con isLoading o usa encadenamiento opcional (data?.field) para evitar errores en tiempo de ejecución.
¿Cómo funciona el patrón stale-while-revalidate en la práctica?
- SWR devuelve datos en caché (obsoletos) de inmediato al renderizar.
- Dispara una solicitud de revalidación en segundo plano.
- Cuando llegan datos frescos, actualiza la UI sin interrupciones.
- El usuario ve contenido al instante mientras los datos se actualizan silenciosamente.