Se você já construiu uma aplicação React com alguma complexidade, provavelmente escreveu o mesmo boilerplate dezenas de vezes: um useEffect para buscar dados, useState para loading e error, invalidação manual de cache quando algo muda. O React Query substitui tudo isso com uma API pequena e bem projetada que lida com cache, refetching em segundo plano e gerenciamento de dados desatualizados out of the box.
O que é React Query?
React Query é uma biblioteca para buscar, armazenar em cache e atualizar o estado do servidor em aplicações React. Ela não substitui Redux ou Zustand para estado client-side; ela lida com uma categoria completamente diferente de estado. O estado do servidor reside em um sistema remoto, pode ficar desatualizado e precisa de sincronização. O React Query foi construído especificamente para esse problema.
Ao lidar com cache e atualizações em segundo plano internamente, ele reduz a quantidade de estado que você gerencia manualmente e torna seus componentes mais fáceis de entender.
Por que usar React Query?
Gerenciar o data fetching manualmente significa rastrear no mínimo três partes do estado por requisição: loading, error e data. Adicione paginação, refetch ao focar na janela, deduplicação de requisições concorrentes e lógica de retry — e você terá centenas de linhas de infraestrutura para um problema que não é o seu produto. O React Query resolve tudo isso no nível da biblioteca.
Vantagens específicas:
- Quando múltiplos componentes solicitam os mesmos dados simultaneamente, o React Query deduplica a requisição de rede e compartilha o resultado.
- Os dados são buscados automaticamente em segundo plano quando ficam desatualizados, então os usuários veem conteúdo atualizado sem um reload manual.
- Atualizações otimistas e invalidação de cache mantêm a UI consistente após mutações sem código de sincronização complexo.
Começando com React Query
Instalação:
npm install @tanstack/react-query
Antes de usar os hooks do React Query, configure um QueryClient e envolva sua aplicação com um QueryClientProvider:
import React from 'react';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
const queryClient = new QueryClient();
function App() {
return (
<QueryClientProvider client={queryClient}>
{/* ... seus componentes */}
</QueryClientProvider>
);
}
export default App;
Entendendo Queries e Mutations
O React Query fornece dois hooks principais: useQuery para buscar dados e useMutation para criar, atualizar ou deletar dados.
O Hook useQuery:
import { useQuery } from '@tanstack/react-query';
function Todos() {
const { isLoading, error, data } = useQuery(['todos'], fetchTodos);
if (isLoading) return 'Loading...';
if (error) return 'An error occurred';
return (
<ul>
{data.map(todo => (
<li key={todo.id}>{todo.title}</li>
))}
</ul>
);
}
O Hook useMutation:
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddTodo() {
const queryClient = useQueryClient();
const mutation = useMutation(addTodo, {
onSuccess: () => {
// Invalidate and refetch
queryClient.invalidateQueries(['todos']);
},
});
return (
<button
onClick={() => {
mutation.mutate({ title: 'New Todo' });
}}
>
Add Todo
</button>
);
}
Configurando um Caso de Uso Completo
Aqui está uma aplicação CRUD básica para gerenciar uma lista de usuários, usando a API JSONPlaceholder.
A aplicação lida com: exibir uma lista de usuários, adicionar um novo usuário, atualizar um usuário existente e deletar um usuário.
Configurando o Projeto:
npx create-react-app react-query-demo
cd react-query-demo
npm install @tanstack/react-query axios
Usaremos Axios para fazer as requisições HTTP.
Implementando React Query na Aplicação:
- Configurar QueryClient
// index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import App from './App';
const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
- Buscar Usuários
// App.js
import React from 'react';
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
function App() {
const { isLoading, error, data } = useQuery(['users'], () =>
axios.get('https://jsonplaceholder.typicode.com/users').then(res => res.data)
);
if (isLoading) return <p>Loading users...</p>;
if (error) return <p>An error occurred: {error.message}</p>;
return (
<div>
<h1>User List</h1>
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
{/* Update and Delete buttons will go here */}
</li>
))}
</ul>
{/* Components for adding a user will go here */}
</div>
);
}
export default App;
- Adicionar um Usuário
Como o JSONPlaceholder não altera dados de verdade, vamos simular a adição de um usuário.
// AddUser.js
import React, { useState } from 'react';
import axios from 'axios';
import { useMutation, useQueryClient } from '@tanstack/react-query';
function AddUser() {
const queryClient = useQueryClient();
const [name, setName] = useState('');
const addUserMutation = useMutation(
newUser => axios.post('https://jsonplaceholder.typicode.com/users', newUser).then(res => res.data),
{
onSuccess: data => {
// Update the users list
queryClient.setQueryData(['users'], oldData => [...oldData, data]);
},
}
);
const handleSubmit = e => {
e.preventDefault();
addUserMutation.mutate({ name });
setName('');
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} placeholder="Name" required />
<button type="submit">Add User</button>
</form>
);
}
export default AddUser;
Inclua <AddUser /> no seu componente App.
// App.js
// ...
import AddUser from './AddUser';
// ...
function App() {
// existing code
return (
<div>
{/* existing code */}
<AddUser />
</div>
);
}
// ...
- Atualizar um Usuário
// UpdateUser.js
import React, { useState } from 'react';
import axios from 'axios';
import { useMutation, useQueryClient } from '@tanstack/react-query';
function UpdateUser({ user }) {
const queryClient = useQueryClient();
const [name, setName] = useState(user.name);
const updateUserMutation = useMutation(
updatedUser => axios.put(`https://jsonplaceholder.typicode.com/users/${user.id}`, updatedUser).then(res => res.data),
{
onSuccess: data => {
queryClient.setQueryData(['users'], oldData =>
oldData.map(u => (u.id === user.id ? data : u))
);
},
}
);
const handleSubmit = e => {
e.preventDefault();
updateUserMutation.mutate({ name });
};
return (
<form onSubmit={handleSubmit}>
<input value={name} onChange={e => setName(e.target.value)} required />
<button type="submit">Update</button>
</form>
);
}
export default UpdateUser;
Inclua <UpdateUser user={user} /> na renderização da sua lista de usuários.
// App.js
// ...
<li key={user.id}>
{user.name}
<UpdateUser user={user} />
{/* Delete button */}
</li>
// ...
- Deletar um Usuário
// DeleteUser.js
import React from 'react';
import axios from 'axios';
import { useMutation, useQueryClient } from '@tanstack/react-query';
function DeleteUser({ userId }) {
const queryClient = useQueryClient();
const deleteUserMutation = useMutation(
() => axios.delete(`https://jsonplaceholder.typicode.com/users/${userId}`),
{
onSuccess: () => {
queryClient.setQueryData(['users'], oldData =>
oldData.filter(user => user.id !== userId)
);
},
}
);
return <button onClick={() => deleteUserMutation.mutate()}>Delete</button>;
}
export default DeleteUser;
Inclua <DeleteUser userId={user.id} /> na renderização da sua lista de usuários.
// App.js
// ...
<li key={user.id}>
{user.name}
<UpdateUser user={user} />
<DeleteUser userId={user.id} />
</li>
// ...
Funcionalidades Avançadas
Lidando com Estados de Query:
O React Query fornece vários estados para gerenciar cenários de loading, error e sucesso de forma eficaz.
const {
isLoading,
isError,
data,
error,
isFetching,
refetch,
} = useQuery(['todos'], fetchTodos);
isLoading: true no primeiro carregamento antes de os dados chegarem.isError: true se a query encontrar um erro.data: o resultado buscado.error: o objeto de erro quando a query falha.isFetching: permanece true durante refetches em segundo plano mesmo quando os dados já estão em cache.refetch: chame isso para disparar um refetch manualmente.
Estratégias de Cache:
useQuery(['users'], fetchUsers, {
cacheTime: 1000 * 60 * 10, // Cache data for 10 minutes
staleTime: 1000 * 60 * 5, // Data is fresh for 5 minutes
});
Polling ou Busca por Intervalo:
useQuery(['notifications'], fetchNotifications, {
refetchInterval: 1000 * 60, // Refetch every 60 seconds
});
Retentando Queries com Falha:
useQuery(['users'], fetchUsers, {
retry: 3, // Retry up to 3 times
retryDelay: attemptIndex => Math.min(1000 * 2 ** attemptIndex, 30000),
});
React Query Devtools:
npm install @tanstack/react-query-devtools
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
function App() {
return (
// ...
<ReactQueryDevtools initialIsOpen={false} />
);
}
Server-Side Rendering:
// pages/_app.js
// Implement getServerSideProps or getStaticProps
Funções de Query Customizadas:
const fetcher = url => axios.get(url).then(res => res.data);
useQuery(['users'], () => fetcher('/api/users'));
Transformando Dados:
useQuery(['users'], fetchUsers, {
select: data => data.map(user => user.name),
});
Boas Práticas
- Use query keys descritivas e estruturadas:
['users', userId]é mais fácil de invalidar seletivamente do que'user'. - Trate erros com callbacks
onErrore os exiba explicitamente para o usuário. - Use paginação ou scroll infinito para grandes conjuntos de dados para evitar carregar tudo de uma vez.
- Ajuste o
staleTimepor query. Dados que raramente mudam podem ter umstaleTimealto; dados em tempo real devem ter zero.
Alternativas ao React Query
SWR, desenvolvido pela Vercel, cobre território similar com uma API mais simples, mas com menos funcionalidades avançadas. O Apollo Client vale a pena considerar se você já usa GraphQL e quer normalização de cache integrada.
Conclusão
O React Query remove uma categoria substancial de boilerplate das aplicações React. Após integrá-lo, você perceberá que a maior parte da sua lógica de data fetching com useEffect desaparece, e os componentes que restam são mais fáceis de testar e entender. A curva de aprendizado está no sistema de query keys e em entender quando invalidar versus atualizar otimisticamente, mas ambos se tornam intuitivos rapidamente.
Leitura Adicional
- React Query Official Documentation
- Handling Authentication with React Query
- Optimistic Updates in React Query