React & Frontend

Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção

12 min de leitura

Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção

SWR em React: Estratégia Stale-While-Revalidate na Prática O que é SWR e por que importa 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. 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. Entendendo o Ciclo de Vida do SWR Como funciona internamente Quando você usa SWR pela primeira vez com uma chave específica, ele segue este fluxo:

<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 &quot;stale&quot;). 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 &quot;carregando&quot;. 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 &#039;swr&#039;;

// Função fetcher que será usada para fazer requisições

const fetcher = (url) =&gt; fetch(url).then(res =&gt; res.json());

export function UserProfile({ userId }) {

// Hook básico do SWR

const { data, error, isLoading } = useSWR(

/api/users/${userId},

fetcher

);

if (isLoading) return &lt;div&gt;Carregando...&lt;/div&gt;;

if (error) return &lt;div&gt;Erro ao carregar: {error.message}&lt;/div&gt;;

return (

&lt;div&gt;

&lt;h1&gt;{data.name}&lt;/h1&gt;

&lt;p&gt;Email: {data.email}&lt;/p&gt;

&lt;/div&gt;

);

}</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 &#039;swr&#039;;

import axios from &#039;axios&#039;;

const fetcher = (url) =&gt; axios.get(url).then(res =&gt; res.data);

export function ProductList() {

const { data: products, mutate } = useSWR(

&#039;/api/products&#039;,

fetcher,

{

revalidateOnFocus: true,

revalidateOnReconnect: true,

dedupingInterval: 60000, // 1 minuto

focusThrottleInterval: 300000, // 5 minutos

errorRetryCount: 3,

errorRetryInterval: 5000,

shouldRetryOnError: true,

}

);

const handleRefresh = () =&gt; {

// Force uma revalidação imediata

mutate();

};

if (!products) return &lt;div&gt;Nenhum produto carregado ainda&lt;/div&gt;;

return (

&lt;div&gt;

&lt;button onClick={handleRefresh}&gt;Atualizar&lt;/button&gt;

{products.map(p =&gt; (

&lt;div key={p.id}&gt;{p.name}&lt;/div&gt;

))}

&lt;/div&gt;

);

}</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 &#039;swr&#039;;

const fetcher = (url) =&gt; fetch(url).then(res =&gt; res.json());

export function TodoItem({ todoId, initialData }) {

const { data: todo, mutate } = useSWR(

/api/todos/${todoId},

fetcher,

{

fallbackData: initialData,

}

);

const handleToggleTodo = async () =&gt; {

// 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: &#039;PATCH&#039;,

headers: { &#039;Content-Type&#039;: &#039;application/json&#039; },

body: JSON.stringify({ completed: newData.completed }),

});

if (!response.ok) throw new Error(&#039;Falha ao atualizar&#039;);

// Revalida para garantir sincronismo com servidor

mutate();

} catch (error) {

// Reverte em caso de erro

mutate(todo, false);

alert(&#039;Erro ao atualizar todo&#039;);

}

};

return (

&lt;div&gt;

&lt;input

type=&quot;checkbox&quot;

checked={todo?.completed}

onChange={handleToggleTodo}

/&gt;

&lt;span&gt;{todo?.title}&lt;/span&gt;

&lt;/div&gt;

);

}</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 &quot;dependent requests&quot; para isso.</p>

<pre><code class="language-jsx">import useSWR from &#039;swr&#039;;

const fetcher = (url) =&gt; fetch(url).then(res =&gt; 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 &lt;div&gt;Carregando usuário...&lt;/div&gt;;

if (!posts) return &lt;div&gt;Carregando posts...&lt;/div&gt;;

return (

&lt;div&gt;

&lt;h1&gt;{user.name}&lt;/h1&gt;

&lt;ul&gt;

{posts.map(post =&gt; (

&lt;li key={post.id}&gt;{post.title}&lt;/li&gt;

))}

&lt;/ul&gt;

&lt;/div&gt;

);

}</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 &#039;swr&#039;;

const fetcher = async (url) =&gt; {

const res = await fetch(url);

if (!res.ok) {

const error = new Error(&#039;API Error&#039;);

error.status = res.status;

error.info = await res.json();

throw error;

}

return res.json();

};

export function Dashboard() {

const { data, error, isLoading } = useSWR(

&#039;/api/dashboard&#039;,

fetcher,

{

onError: (err) =&gt; {

console.error(Erro ${err.status}:, err.info);

},

onErrorRetry: (error, key, config, retryer, { retryCount }) =&gt; {

if (error.status === 404) return; // Não tenta novamente

if (retryCount &gt;= 3) return; // Máximo de tentativas

setTimeout(() =&gt; retryer({ retryCount }), 1000);

},

}

);

if (isLoading) return &lt;div&gt;Carregando dashboard...&lt;/div&gt;;

if (error?.status === 404) return &lt;div&gt;Dashboard não encontrado&lt;/div&gt;;

if (error) return &lt;div&gt;Erro: {error.message}&lt;/div&gt;;

return &lt;div&gt;{/ Renderizar dados /}&lt;/div&gt;;

}</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 &#039;swr&#039;;

const fetcher = (url) =&gt; fetch(url).then(res =&gt; res.json());

// Componente A

function Header() {

const { data: user } = useSWR(&#039;/api/me&#039;, fetcher);

return &lt;h1&gt;Olá, {user?.name}&lt;/h1&gt;;

}

// Componente B

function Sidebar() {

const { data: user } = useSWR(&#039;/api/me&#039;, fetcher);

return &lt;div&gt;Avatar: {user?.avatar}&lt;/div&gt;;

}

// Ambos fazem a mesma requisição, mas apenas uma requisição HTTP é feita

export function App() {

return (

&lt;div&gt;

&lt;Header /&gt;

&lt;Sidebar /&gt;

&lt;/div&gt;

);

}</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 &#039;swr&#039;;

import { useState } from &#039;react&#039;;

const fetcher = (url) =&gt; fetch(url).then(res =&gt; res.json());

export function UsersList() {

const [page, setPage] = useState(1);

const { data, mutate } = useSWR(

/api/users?page=${page}&amp;limit=10,

fetcher

);

const handleNextPage = () =&gt; {

setPage(page + 1);

};

const handlePreviousPage = () =&gt; {

if (page &gt; 1) setPage(page - 1);

};

if (!data) return &lt;div&gt;Carregando...&lt;/div&gt;;

return (

&lt;div&gt;

{data.users.map(user =&gt; (

&lt;div key={user.id}&gt;{user.name}&lt;/div&gt;

))}

&lt;button onClick={handlePreviousPage} disabled={page === 1}&gt;

Anterior

&lt;/button&gt;

&lt;button onClick={handleNextPage} disabled={!data.hasMore}&gt;

Próximo

&lt;/button&gt;

&lt;/div&gt;

);

}</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>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Boas Práticas de React Query: Mutations, Invalidações e Sincronização de Cache para Times Ágeis
Boas Práticas de React Query: Mutations, Invalidações e Sincronização de Cache para Times Ágeis

Introdução ao React Query e o Ciclo de Vida das Mutações React Query é uma bi...

Hooks para Animações: useSpring, useAnimate e Física de Movimento na Prática
Hooks para Animações: useSpring, useAnimate e Física de Movimento na Prática

Entendendo Hooks para Animações no React Animações são fundamentais para cria...

O que Todo Dev Deve Saber sobre useRef Avançado: DOM, Valores Mutáveis e Comunicação entre Renders
O que Todo Dev Deve Saber sobre useRef Avançado: DOM, Valores Mutáveis e Comunicação entre Renders

O que é useRef e por que vai além de useState O é um hook do React que retorn...