<h2>SWR em React: Estratégia Stale-While-Revalidate na Prática</h2>
<h3>O que é SWR e por que importa</h3>
<p>SWR (Stale-While-Revalidate) é uma estratégia de cache HTTP que permite servir dados em cache (mesmo que desatualizados) imediatamente ao usuário, enquanto faz uma requisição em segundo plano para atualizar esses dados. Em React, a biblioteca SWR implementa esse padrão de forma elegante e produtiva, reduzindo a latência percebida e melhorando a experiência do usuário.</p>
<p>A ideia central é simples: não force o usuário a esperar por dados frescos. Mostre o que você tem agora e atualize em background. Isso é particularmente poderoso em aplicações que lidam com conexões lentas ou instáveis, onde a percepção de performance importa tanto quanto a performance real. A biblioteca SWR, mantida pela Vercel, abstrai toda a complexidade dessa estratégia, oferecendo um hook simples e intuitivo.</p>
<h2>Entendendo o Ciclo de Vida do SWR</h2>
<h3>Como funciona internamente</h3>
<p>Quando você usa SWR pela primeira vez com uma chave específica, ele segue este fluxo: primeiro, verifica se existe dados em cache; se existirem, retorna imediatamente (estado "stale"). Em paralelo, faz uma requisição para o servidor. Quando a resposta chega, atualiza o cache e o componente re-renderiza com os dados frescos. Se nenhum cache existir, ele aguarda a primeira resposta antes de renderizar.</p>
<p>Este comportamento elimina aquele padrão comum onde você mostra um skeleton loader ou message de "carregando". Com SWR, você pode mostrar dados anteriores enquanto revalida, criando uma experiência fluida. Entender esse ciclo é crucial para aproveitar o máximo potencial da biblioteca.</p>
<h3>Exemplo básico funcional</h3>
<pre><code class="language-jsx">import useSWR from 'swr';
// Função fetcher que será usada para fazer requisições
const fetcher = (url) => fetch(url).then(res => res.json());
export function UserProfile({ userId }) {
// Hook básico do SWR
const { data, error, isLoading } = useSWR(
/api/users/${userId},
fetcher
);
if (isLoading) return <div>Carregando...</div>;
if (error) return <div>Erro ao carregar: {error.message}</div>;
return (
<div>
<h1>{data.name}</h1>
<p>Email: {data.email}</p>
</div>
);
}</code></pre>
<p>Note que <code>isLoading</code> é true apenas na primeira requisição. Nas subsequentes, dados anteriores são mostrados imediatamente enquanto a revalidação acontece em background. Isso é o SWR em ação.</p>
<h2>Configuração Avançada e Otimizações</h2>
<h3>Opções essenciais</h3>
<p>SWR oferece dúzias de opções, mas algumas são críticas para produção. A opção <code>revalidateOnFocus</code> controla se os dados devem ser revalidados quando a aba volta ao foco — ideal para manter dados sempre frescos. <code>dedupingInterval</code> define quanto tempo (em ms) requisições duplicadas são deduplificadas, economizando banda. <code>focusThrottleInterval</code> evita múltiplas revalidações rápidas.</p>
<p>Outra opção importante é <code>errorRetryCount</code> e <code>errorRetryInterval</code>, que definem quantas vezes tentar novamente em caso de erro e com qual intervalo. Em produção, você provavelmente quer um <code>revalidateIfStale: true</code> para sempre revalidar se os dados estão muito antigos.</p>
<pre><code class="language-jsx">import useSWR from 'swr';
import axios from 'axios';
const fetcher = (url) => axios.get(url).then(res => res.data);
export function ProductList() {
const { data: products, mutate } = useSWR(
'/api/products',
fetcher,
{
revalidateOnFocus: true,
revalidateOnReconnect: true,
dedupingInterval: 60000, // 1 minuto
focusThrottleInterval: 300000, // 5 minutos
errorRetryCount: 3,
errorRetryInterval: 5000,
shouldRetryOnError: true,
}
);
const handleRefresh = () => {
// Force uma revalidação imediata
mutate();
};
if (!products) return <div>Nenhum produto carregado ainda</div>;
return (
<div>
<button onClick={handleRefresh}>Atualizar</button>
{products.map(p => (
<div key={p.id}>{p.name}</div>
))}
</div>
);
}</code></pre>
<h3>Mutação otimista (Optimistic UI)</h3>
<p>Um dos padrões mais poderosos é a mutação otimista: atualizar a UI imediatamente enquanto a requisição está em flight. Se falhar, você reverte. Isso cria uma sensação de resposta instantânea.</p>
<pre><code class="language-jsx">import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export function TodoItem({ todoId, initialData }) {
const { data: todo, mutate } = useSWR(
/api/todos/${todoId},
fetcher,
{
fallbackData: initialData,
}
);
const handleToggleTodo = async () => {
// Estado otimista: atualiza localmente
const newData = { ...todo, completed: !todo.completed };
mutate(newData, false); // false = não revalidar
try {
const response = await fetch(/api/todos/${todoId}, {
method: 'PATCH',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ completed: newData.completed }),
});
if (!response.ok) throw new Error('Falha ao atualizar');
// Revalida para garantir sincronismo com servidor
mutate();
} catch (error) {
// Reverte em caso de erro
mutate(todo, false);
alert('Erro ao atualizar todo');
}
};
return (
<div>
<input
type="checkbox"
checked={todo?.completed}
onChange={handleToggleTodo}
/>
<span>{todo?.title}</span>
</div>
);
}</code></pre>
<p>Aqui, <code>mutate(newData, false)</code> atualiza a UI imediatamente sem fazer fetch. Depois tentamos sincronizar com o servidor. Se falhar, revertemos com <code>mutate(todo, false)</code>.</p>
<h2>Padrões de Produção</h2>
<h3>Múltiplas requisições correlacionadas</h3>
<p>Frequentemente você precisa fazer várias requisições que dependem uma da outra. SWR oferece o padrão de "dependent requests" para isso.</p>
<pre><code class="language-jsx">import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
export function UserWithPosts({ userId }) {
// Primeira requisição: dados do usuário
const { data: user } = useSWR(
userId ? /api/users/${userId} : null,
fetcher
);
// Segunda requisição: posts do usuário (depende da primeira)
const { data: posts } = useSWR(
user ? /api/users/${user.id}/posts : null,
fetcher
);
if (!user) return <div>Carregando usuário...</div>;
if (!posts) return <div>Carregando posts...</div>;
return (
<div>
<h1>{user.name}</h1>
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
</div>
);
}</code></pre>
<p>Passando <code>null</code> como chave, o SWR não faz a requisição. Isso permite criar dependências naturalmente.</p>
<h3>Tratamento robusto de erros</h3>
<pre><code class="language-jsx">import useSWR from 'swr';
const fetcher = async (url) => {
const res = await fetch(url);
if (!res.ok) {
const error = new Error('API Error');
error.status = res.status;
error.info = await res.json();
throw error;
}
return res.json();
};
export function Dashboard() {
const { data, error, isLoading } = useSWR(
'/api/dashboard',
fetcher,
{
onError: (err) => {
console.error(Erro ${err.status}:, err.info);
},
onErrorRetry: (error, key, config, retryer, { retryCount }) => {
if (error.status === 404) return; // Não tenta novamente
if (retryCount >= 3) return; // Máximo de tentativas
setTimeout(() => retryer({ retryCount }), 1000);
},
}
);
if (isLoading) return <div>Carregando dashboard...</div>;
if (error?.status === 404) return <div>Dashboard não encontrado</div>;
if (error) return <div>Erro: {error.message}</div>;
return <div>{/ Renderizar dados /}</div>;
}</code></pre>
<p>A callback <code>onErrorRetry</code> oferece controle fino sobre quando e como retentar. Isso é essencial para lidar com diferentes tipos de erro (4xx vs 5xx) de forma apropriada.</p>
<h2>Casos de Uso Reais</h2>
<h3>Cache compartilhado entre componentes</h3>
<p>Um benefício subestimado do SWR é o cache global. Se dois componentes usam a mesma chave, eles compartilham os dados sem requisições duplicadas.</p>
<pre><code class="language-jsx">import useSWR from 'swr';
const fetcher = (url) => fetch(url).then(res => res.json());
// Componente A
function Header() {
const { data: user } = useSWR('/api/me', fetcher);
return <h1>Olá, {user?.name}</h1>;
}
// Componente B
function Sidebar() {
const { data: user } = useSWR('/api/me', fetcher);
return <div>Avatar: {user?.avatar}</div>;
}
// Ambos fazem a mesma requisição, mas apenas uma requisição HTTP é feita
export function App() {
return (
<div>
<Header />
<Sidebar />
</div>
);
}</code></pre>
<p>SWR deduplica automaticamente requisições idênticas dentro de um curto período. Isso reduz carga no servidor e melhora performance.</p>
<h3>Paginação eficiente</h3>
<pre><code class="language-jsx">import useSWR from 'swr';
import { useState } from 'react';
const fetcher = (url) => fetch(url).then(res => res.json());
export function UsersList() {
const [page, setPage] = useState(1);
const { data, mutate } = useSWR(
/api/users?page=${page}&limit=10,
fetcher
);
const handleNextPage = () => {
setPage(page + 1);
};
const handlePreviousPage = () => {
if (page > 1) setPage(page - 1);
};
if (!data) return <div>Carregando...</div>;
return (
<div>
{data.users.map(user => (
<div key={user.id}>{user.name}</div>
))}
<button onClick={handlePreviousPage} disabled={page === 1}>
Anterior
</button>
<button onClick={handleNextPage} disabled={!data.hasMore}>
Próximo
</button>
</div>
);
}</code></pre>
<p>Cada mudança de <code>page</code> causa uma nova chave no SWR, disparando uma nova requisição. O cache de páginas anteriores permanece intacto, permitindo navegação rápida.</p>
<h2>Conclusão</h2>
<p>Durante este artigo, cobrimos três pilares fundamentais: <strong>primeiro</strong>, entender o ciclo stale-while-revalidate não como um conceito abstrato, mas como um fluxo concreto que melhora UX ao servir dados em cache enquanto revalida em background. <strong>Segundo</strong>, dominar as opções de configuração e o padrão de mutação otimista, transformando SWR de um simples fetcher em uma estratégia completa de gerenciamento de estado assíncrono. <strong>Terceiro</strong>, reconhecer que SWR resolve problemas reais de deduplição, cache compartilhado e sincronização que você precisaria implementar manualmente com fetch ou axios puro. Com esses conhecimentos, você está preparado para construir aplicações React que não apenas carregam dados, mas que <em>sentem</em> rápidas e responsivas.</p>
<h2>Referências</h2>
<ul>
<li><a href="https://swr.vercel.app/" target="_blank" rel="noopener noreferrer">Documentação oficial do SWR</a></li>
<li><a href="https://github.com/vercel/swr" target="_blank" rel="noopener noreferrer">SWR GitHub Repository</a></li>
<li><a href="https://tools.ietf.org/html/rfc5861" target="_blank" rel="noopener noreferrer">RFC 5861: HTTP Cache-Control Extensions for Stale Content</a></li>
<li><a href="https://vercel.com/blog/swr" target="_blank" rel="noopener noreferrer">React Data Fetching Patterns with SWR</a></li>
<li><a href="https://www.smashingmagazine.com/2022/02/progressive-enhancement-fetching-remote-data-react/" target="_blank" rel="noopener noreferrer">Optimistic UI Updates with SWR</a></li>
</ul>
<p><!-- FIM --></p>