React & Frontend

Guia Completo de URL como Estado em React: useSearchParams e Sincronização de Filtros

12 min de leitura

Guia Completo de URL como Estado em React: useSearchParams e Sincronização de Filtros

Por que Sincronizar Estado com a URL? Quando você trabalha com filtros, paginação ou qualquer tipo de estado que afete a visualização de dados em uma aplicação React, uma decisão fundamental é: onde armazenar esse estado? A resposta mais sofisticada não é apenas no estado local do componente — é na URL. Sincronizar estado com a URL resolve problemas reais. Se um usuário aplica um conjunto de filtros em sua aplicação e depois compartilha o link com um colega, espera-se que esse colega veja exatamente a mesma página com os mesmos filtros aplicados. Além disso, o botão voltar do navegador funcionará intuitivamente, permitindo navegar pelo histórico de estados anteriores. Sem essa sincronização, você força o usuário a refazer suas ações toda vez que recarrega a página ou clica em voltar. A URL é um banco de dados público e acessível. Ela persiste através de recarregamentos, funciona com favoritos, é compartilhável e integra-se perfeitamente com o comportamento natural do navegador. Por

<h2>Por que Sincronizar Estado com a URL?</h2>

<p>Quando você trabalha com filtros, paginação ou qualquer tipo de estado que afete a visualização de dados em uma aplicação React, uma decisão fundamental é: onde armazenar esse estado? A resposta mais sofisticada não é apenas no estado local do componente — é na URL.</p>

<p>Sincronizar estado com a URL resolve problemas reais. Se um usuário aplica um conjunto de filtros em sua aplicação e depois compartilha o link com um colega, espera-se que esse colega veja exatamente a mesma página com os mesmos filtros aplicados. Além disso, o botão voltar do navegador funcionará intuitivamente, permitindo navegar pelo histórico de estados anteriores. Sem essa sincronização, você força o usuário a refazer suas ações toda vez que recarrega a página ou clica em voltar.</p>

<p>A URL é um banco de dados público e acessível. Ela persiste através de recarregamentos, funciona com favoritos, é compartilhável e integra-se perfeitamente com o comportamento natural do navegador. Por essas razões, usar <code>useSearchParams</code> do React Router para gerenciar filtros e paginação é uma prática profissional essencial.</p>

<h2>useSearchParams: Conceito e Funcionamento</h2>

<h3>O que é useSearchParams?</h3>

<p><code>useSearchParams</code> é um hook fornecido pelo React Router que permite ler e modificar os parâmetros de consulta (query string) da URL. Se sua URL é <code>https://app.com/produtos?categoria=eletrônicos&amp;pagina=2</code>, <code>useSearchParams</code> acessa e manipula aquela parte <code>?categoria=eletrônicos&amp;pagina=2</code>.</p>

<p>Esse hook retorna um array com dois elementos: um objeto <code>URLSearchParams</code> contendo os parâmetros atuais, e uma função para atualizar esses parâmetros. Diferentemente de <code>useState</code>, essas alterações também atualizam a URL do navegador, mantendo tudo sincronizado.</p>

<h3>Diferença entre useState e useSearchParams</h3>

<p>Imagine que você quer armazenar um filtro de categoria. Com <code>useState</code>, você faz:</p>

<pre><code class="language-javascript">const [categoria, setCategoria] = useState(&#039;&#039;);</code></pre>

<p>Funciona, mas quando você recarrega a página, o estado volta ao padrão. A URL permanece <code>https://app.com/produtos</code> sem qualquer informação sobre a seleção anterior. Com <code>useSearchParams</code>, você toma uma decisão diferente:</p>

<pre><code class="language-javascript">const [searchParams, setSearchParams] = useSearchParams();

const categoria = searchParams.get(&#039;categoria&#039;) || &#039;&#039;;</code></pre>

<p>Agora, quando o usuário muda de categoria, a URL muda para <code>https://app.com/produtos?categoria=eletrônicos</code>. Se ele recarrega a página ou volta a ela depois, o filtro persiste porque está armazenado na URL.</p>

<h2>Implementando Filtros com Sincronização de URL</h2>

<h3>Exemplo Prático: Listagem de Produtos com Filtros</h3>

<p>Vamos construir uma página de produtos com filtros por categoria e preço, onde tudo está sincronizado com a URL. Este é um cenário real que você encontrará em projetos profissionais.</p>

<pre><code class="language-javascript">import { useSearchParams } from &#039;react-router-dom&#039;;

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

function ProdutosList() {

const [searchParams, setSearchParams] = useSearchParams();

const [produtos, setProdutos] = useState([]);

const [carregando, setCarregando] = useState(false);

// Obtém os valores atuais da URL, ou usa valores padrão

const categoria = searchParams.get(&#039;categoria&#039;) | | &#039;&#039;; const precoMin = searchParams.get(&#039;precoMin&#039;) || &#039;&#039;; const precoMax = searchParams.get(&#039;precoMax&#039;) || &#039;&#039;; const pagina = parseInt(searchParams.get(&#039;pagina&#039;) || &#039;1&#039;, 10);

// Busca produtos quando os filtros mudam

useEffect(() =&gt; {

setCarregando(true);

// Simula uma chamada à API

const params = new URLSearchParams({

...(categoria &amp;&amp; { categoria }),

...(precoMin &amp;&amp; { precoMin }),

...(precoMax &amp;&amp; { precoMax }),

pagina,

limite: 10

});

fetch(/api/produtos?${params})

.then(res =&gt; res.json())

.then(data =&gt; setProdutos(data))

.catch(err =&gt; console.error(err))

.finally(() =&gt; setCarregando(false));

}, [categoria, precoMin, precoMax, pagina]);

// Atualiza um filtro na URL

const handleFiltroChange = (chave, valor) =&gt; {

const novoParams = new URLSearchParams(searchParams);

if (valor === &#039;&#039; || valor === null) {

novoParams.delete(chave);

} else {

novoParams.set(chave, valor);

}

// Reseta para página 1 quando filtro muda

novoParams.set(&#039;pagina&#039;, &#039;1&#039;);

setSearchParams(novoParams);

};

// Muda de página

const handleMudarPagina = (novaPagina) =&gt; {

const novoParams = new URLSearchParams(searchParams);

novoParams.set(&#039;pagina&#039;, novaPagina);

setSearchParams(novoParams);

};

return (

&lt;div className=&quot;container&quot;&gt;

&lt;section className=&quot;filtros&quot;&gt;

&lt;h2&gt;Filtros&lt;/h2&gt;

&lt;label&gt;

Categoria:

&lt;select

value={categoria}

onChange={(e) =&gt; handleFiltroChange(&#039;categoria&#039;, e.target.value)}

&gt;

&lt;option value=&quot;&quot;&gt;Todas&lt;/option&gt;

&lt;option value=&quot;eletrônicos&quot;&gt;Eletrônicos&lt;/option&gt;

&lt;option value=&quot;roupas&quot;&gt;Roupas&lt;/option&gt;

&lt;option value=&quot;livros&quot;&gt;Livros&lt;/option&gt;

&lt;/select&gt;

&lt;/label&gt;

&lt;label&gt;

Preço Mínimo:

&lt;input

type=&quot;number&quot;

value={precoMin}

onChange={(e) =&gt; handleFiltroChange(&#039;precoMin&#039;, e.target.value)}

placeholder=&quot;0&quot;

/&gt;

&lt;/label&gt;

&lt;label&gt;

Preço Máximo:

&lt;input

type=&quot;number&quot;

value={precoMax}

onChange={(e) =&gt; handleFiltroChange(&#039;precoMax&#039;, e.target.value)}

placeholder=&quot;1000&quot;

/&gt;

&lt;/label&gt;

&lt;button onClick={() =&gt; {

const novoParams = new URLSearchParams();

novoParams.set(&#039;pagina&#039;, &#039;1&#039;);

setSearchParams(novoParams);

}}&gt;

Limpar Filtros

&lt;/button&gt;

&lt;/section&gt;

&lt;section className=&quot;produtos&quot;&gt;

{carregando ? (

&lt;p&gt;Carregando...&lt;/p&gt;

) : produtos.length === 0 ? (

&lt;p&gt;Nenhum produto encontrado&lt;/p&gt;

) : (

&lt;&gt;

&lt;ul&gt;

{produtos.map(produto =&gt; (

&lt;li key={produto.id}&gt;

&lt;h3&gt;{produto.nome}&lt;/h3&gt;

&lt;p&gt;R$ {produto.preco.toFixed(2)}&lt;/p&gt;

&lt;span className=&quot;categoria&quot;&gt;{produto.categoria}&lt;/span&gt;

&lt;/li&gt;

))}

&lt;/ul&gt;

&lt;div className=&quot;paginacao&quot;&gt;

&lt;button

disabled={pagina === 1}

onClick={() =&gt; handleMudarPagina(pagina - 1)}

&gt;

Anterior

&lt;/button&gt;

&lt;span&gt;Página {pagina}&lt;/span&gt;

&lt;button

onClick={() =&gt; handleMudarPagina(pagina + 1)}

&gt;

Próxima

&lt;/button&gt;

&lt;/div&gt;

&lt;/&gt;

)}

&lt;/section&gt;

&lt;/div&gt;

);

}

export default ProdutosList;</code></pre>

<h3>Como o Código Funciona</h3>

<p>A lógica é simples mas poderosa. Cada filtro lê seu valor da URL através de <code>searchParams.get()</code>. Quando o usuário muda um filtro, <code>handleFiltroChange</code> cria uma cópia dos parâmetros atuais, modifica o que mudou e chama <code>setSearchParams</code> para atualizar a URL. O <code>useEffect</code> observa todas essas mudanças e dispara uma nova busca na API.</p>

<p>Observe que ao limpar filtros, reseta-se a página para 1. Isso previne confusão onde um usuário está na página 5, limpa os filtros e não vê nada porque a página 5 pode não existir com os novos filtros. É um detalhe que profissionais sempre implementam.</p>

<h2>Padrões Avançados e Boas Práticas</h2>

<h3>Sincronização com Estados Derivados</h3>

<p>Em aplicações mais complexas, você pode ter múltiplas fontes de estado: a URL, o estado local, dados em cache. A chave é estabelecer a URL como a fonte única de verdade. Nunca replique o valor da URL em <code>useState</code> — sempre leia diretamente de <code>searchParams</code>.</p>

<pre><code class="language-javascript"></code></pre>

<h3>Debouncing para Filtros em Tempo Real</h3>

<p>Se você tem um campo de busca por texto que deveria atualizar a URL a cada digitação, sem debouncing a URL será reescrita dezenas de vezes por segundo. Use <code>useEffect</code> com debouncing:</p>

<pre><code class="language-javascript">import { useSearchParams } from &#039;react-router-dom&#039;;

import { useEffect, useRef } from &#039;react&#039;;

function BuscaProdutos() {

const [searchParams, setSearchParams] = useSearchParams();

const [busca, setBusca] = useState(searchParams.get(&#039;q&#039;) || &#039;&#039;);

const timerRef = useRef(null);

useEffect(() =&gt; {

// Limpa timer anterior

if (timerRef.current) {

clearTimeout(timerRef.current);

}

// Aguarda 500ms após o usuário parar de digitar

timerRef.current = setTimeout(() =&gt; {

const novoParams = new URLSearchParams(searchParams);

if (busca.trim()) {

novoParams.set(&#039;q&#039;, busca);

} else {

novoParams.delete(&#039;q&#039;);

}

setSearchParams(novoParams);

}, 500);

return () =&gt; clearTimeout(timerRef.current);

}, [busca, searchParams, setSearchParams]);

return (

&lt;input

type=&quot;text&quot;

value={busca}

onChange={(e) =&gt; setBusca(e.target.value)}

placeholder=&quot;Buscar produtos...&quot;

/&gt;

);

}</code></pre>

<h3>Preservando Filtros ao Navegar</h3>

<p>Quando um usuário clica em um produto para ver detalhes e depois volta, você quer que os filtros anteriores estejam lá. Isso funciona naturalmente com <code>useSearchParams</code> porque a URL é preservada no histórico do navegador. O botão voltar restaura a URL anterior e seus parâmetros automaticamente.</p>

<p>Para ir além, você pode criar um helper que sanitiza e valida parâmetros:</p>

<pre><code class="language-javascript">function useFiltrosValidos(opcoesValidas) {

const [searchParams, setSearchParams] = useSearchParams();

const filtrosSanitizados = Object.fromEntries(

Array.from(searchParams.entries()).filter(([chave, valor]) =&gt; {

const config = opcoesValidas[chave];

if (!config) return false;

if (config.tipo === &#039;enum&#039;) {

return config.valores.includes(valor);

}

if (config.tipo === &#039;numero&#039;) {

return !isNaN(valor) &amp;&amp; valor &gt;= config.min &amp;&amp; valor &lt;= config.max;

}

return true;

})

);

return [filtrosSanitizados, setSearchParams];

}

// Uso

const opcoesValidas = {

categoria: { tipo: &#039;enum&#039;, valores: [&#039;eletrônicos&#039;, &#039;roupas&#039;, &#039;livros&#039;] },

precoMin: { tipo: &#039;numero&#039;, min: 0, max: 10000 },

pagina: { tipo: &#039;numero&#039;, min: 1, max: Infinity }

};

const [filtros] = useFiltrosValidos(opcoesValidas);</code></pre>

<h2>Conclusão</h2>

<p>Aprendemos que <strong>sincronizar estado com a URL via <code>useSearchParams</code> resolve problemas fundamentais de compartilhamento, persistência e navegabilidade</strong> que toda aplicação profissional deve resolver. Não é apenas uma feature técnica — é uma expectativa dos usuários modernos.</p>

<p>Em segundo lugar, <strong>a URL deve ser a fonte única de verdade para filtros e paginação</strong>. Nunca duplique esses valores em <code>useState</code> paralelo. Sempre leia de <code>searchParams</code> e escreva através de <code>setSearchParams</code>.</p>

<p>Por fim, <strong>proteja sua aplicação contra parâmetros inválidos e implemente padrões como debouncing para campos dinâmicos</strong>. Profissionais entendem que código que funciona no caso feliz é apenas o começo — é a robustez diante de casos extremos que faz a diferença.</p>

<h2>Referências</h2>

<ul>

<li><a href="https://reactrouter.com/en/main/hooks/use-search-params" target="_blank" rel="noopener noreferrer">React Router: useSearchParams Documentation</a></li>

<li><a href="https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams" target="_blank" rel="noopener noreferrer">MDN Web Docs: URLSearchParams API</a></li>

<li><a href="https://reactrouter.com/en/main/start/overview" target="_blank" rel="noopener noreferrer">React Router: Location State Pattern</a></li>

<li><a href="https://web.dev/bfcache/" target="_blank" rel="noopener noreferrer">Web.dev: Sharing URLs with state</a></li>

</ul>

<p>&lt;!-- FIM --&gt;</p>

Comentários

Mais em React & Frontend

Implementando um Mini React do Zero: createElement, render e Hooks: Do Básico ao Avançado
Implementando um Mini React do Zero: createElement, render e Hooks: Do Básico ao Avançado

Entendendo a Arquitetura do React React é uma biblioteca que revolucionou a f...

Como Usar SWR em React: Estratégia Stale-While-Revalidate na Prática em Produção
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...

Guia Completo de Arquiteturas de Estado em React: Local, Global, Server e URL State
Guia Completo de Arquiteturas de Estado em React: Local, Global, Server e URL State

Introdução: Os Quatro Pilares do Gerenciamento de Estado O gerenciamento de e...