Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
SWR captura los errores lanzados por tu fetcher y los expone mediante el valor de retorno error. Configura reintentos automáticos, callbacks de error e integra con React Error Boundaries para un manejo robusto de errores.
"use client";
import useSWR from "swr";
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error("Ocurrió un error al obtener los datos.");
(error as any).info = await res.json();
(error as any).status = res.status;
throw error;
}
return res.json();
};
function UserProfile({ id }: { id: string }) {
const { data, error } = useSWR(`/api/users/${id}`, fetcher, {
onError: (err) => console.error("SWR error:", err),
shouldRetryOnError: true,
errorRetryCount: 3,
errorRetryInterval: 5000,
});
if (error) return <div>Error: {error.message}</div>;
return <div>{data?.name}</div>;
}"use client";
import useSWR from "swr";
import { Component, ReactNode } from "react";
// Clase de error personalizada con contexto adicional
class ApiError extends Error {
status: number;
info: Record<string, unknown>;
constructor(message: string, status: number, info: Record<string, unknown>) {
super(message);
this.status = status;
this.info = info;
}
}
const fetcher = async (url: string) => {
const res = await fetch(url);
if (!res.ok) {
const info = await res.json().catch(() => ({}));
throw new ApiError(
`API error: ${res.statusText}`,
res.status,
info
);
}
return res.json();
};
// Componente Error Boundary
class ErrorBoundary extends Component<
{ children: ReactNode; fallback: ReactNode },
{ hasError: boolean }
> {
state = { hasError: false };
static getDerivedStateFromError() {
return { hasError: true };
}
render() {
if (this.state.hasError) return this.props.fallback;
return this.props.children;
}
}
function OrderDetails({ orderId }: { orderId: string }) {
const { data, error, isLoading } = useSWR(`/api/orders/${orderId}`, fetcher, {
shouldRetryOnError: (err: ApiError) => err.status !== 404,
errorRetryCount: 3,
errorRetryInterval: 2000,
onError: (err: ApiError) => {
if (err.status === 401) {
window.location.href = "/login";
}
},
});
if (isLoading) return <div>Cargando pedido...</div>;
if (error) {
if (error instanceof ApiError && error.status === 404) {
return <div>Pedido no encontrado</div>;
}
return <div>Algo salió mal: {error.message}</div>;
}
return (
<div>
<h2>Pedido #{data.id}</h2>
<p>Estado: {data.status}</p>
<p>Total: ${data.total}</p>
</div>
);
}
export default function OrderPage({ orderId }: { orderId: string }) {
return (
<ErrorBoundary fallback={<div>Algo salió mal</div>}>
<OrderDetails orderId={orderId} />
</ErrorBoundary>
);
}error.errorRetryInterval.shouldRetryOnError puede ser true, false o una función (err) => boolean para lógica de reintento condicional.errorRetryCount limita el número total de intentos de reintento (por defecto: ilimitado en conexiones lentas).onError(err, key, config) se dispara en cada error, incluidos los reintentos.data incluso cuando una revalidación falla. Esto significa que data y error pueden estar definidos simultáneamente.onErrorRetry ofrece control total sobre el comportamiento de reintento, incluido el tiempo y la lógica de aborto.Reintento personalizado con backoff:
const { data } = useSWR("/api/data", fetcher, {
onErrorRetry: (error, key, config, revalidate, { retryCount }) => {
// Nunca reintentar en 404
if (error.status === 404) return;
// Detener tras 5 reintentos
if (retryCount >= 5) return;
// Backoff exponencial
setTimeout(() => revalidate({ retryCount }), Math.min(1000 * 2 ** retryCount, 30000));
},
});Manejador global de errores:
<SWRConfig
value={{
onError: (error, key) => {
if (error.status !== 403 && error.status !== 404) {
reportToSentry(error, { key });
}
},
}}
>
{children}
</SWRConfig>Estado de error con datos obsoletos:
function Dashboard() {
const { data, error, isValidating } = useSWR("/api/stats", fetcher);
return (
<div>
{error && (
<div className="bg-yellow-100 p-2">
No se pudo actualizar. Mostrando los últimos datos conocidos.
{isValidating && " Reintentando..."}
</div>
)}
{data && <StatsDisplay stats={data} />}
</div>
);
}useSWR<Data, Error>(key, fetcher).const { data, error } = useSWR<User, ApiError>("/api/me", fetcher);
if (error) {
// error está tipado como ApiError
console.log(error.status); // number
console.log(error.info); // Record<string, unknown>
}error. Valida siempre res.ok en tu fetcher.data y error pueden ser ambos truthy al mismo tiempo. Esto ocurre cuando una revalidación falla pero existen datos en caché. No asumas que son mutuamente excluyentes.errorRetryCount.onError se dispara en cada evento de error, incluidos los reintentos, lo que puede saturar los servicios de reporte de errores. Aplica debounce o deduplica en tu manejador.| Enfoque | Ventajas | Desventajas |
|---|---|---|
| Valor de retorno error de SWR | Declarativo, por componente | Hay que manejarlo en cada componente |
| Error Boundary | Captura errores en tiempo de renderizado | No captura errores asíncronos de SWR de forma nativa |
| Callback global onError | Seguimiento centralizado de errores | No puede afectar el renderizado de componentes individuales |
| Notificaciones toast | Feedback no bloqueante para el usuario | El usuario podría pasar por alto la notificación |
SWR reintenta con backoff exponencial: 1s, 2s, 4s, 8s, etc., limitado por errorRetryInterval. El reintento está habilitado por defecto sin límite máximo a menos que establezcas errorRetryCount.
const { data } = useSWR("/api/data", fetcher, {
shouldRetryOnError: (err) => err.status !== 404,
});O usa onErrorRetry para control total sobre la lógica de reintento por tipo de error.
Sí. Cuando una revalidación falla pero existen datos en caché, tanto data como error son truthy. No asumas que son mutuamente excluyentes. Muestra datos obsoletos con un banner de error para la mejor experiencia de usuario.
Los Error Boundaries capturan errores durante el renderizado, no errores asíncronos. Los errores de SWR son asíncronos y no se propagarán a Error Boundaries a menos que los relances durante el renderizado o uses el modo suspense: true.
class ApiError extends Error {
status: number;
info: Record<string, unknown>;
constructor(message: string, status: number, info: Record<string, unknown>) {
super(message);
this.status = status;
this.info = info;
}
}Lánzala en tu fetcher cuando res.ok sea false.
<SWRConfig
value={{
onError: (error, key) => {
if (error.status !== 403 && error.status !== 404) {
reportToSentry(error, { key });
}
},
}}
>
{children}
</SWRConfig>onError se dispara en cada evento de error, incluido cada intento de reintento. Esto puede saturar los servicios de reporte de errores. Aplica debounce o deduplica los reportes de error en tu manejador, o limita los reintentos con errorRetryCount.
SWR nunca establecerá error. El cuerpo de la respuesta se trata como datos exitosos. Comprueba siempre res.ok en tu fetcher y lanza un error para códigos de estado distintos de 2xx.
Pasa el tipo de error como segundo genérico:
const { data, error } = useSWR<User, ApiError>("/api/me", fetcher);
if (error) {
console.log(error.status); // tipado como number
}Si omites el genérico de error, el valor por defecto es any.
data como error en tu componente.isValidating para mostrar un indicador de "Reintentando...".Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥