This article is published in English.
What is and How to React Query? for production systems
Operable walkthrough of What is and How to React Query? for production systems: contracts, checks, and drop-in code slots for teams shipping this pattern.
The following notes reconstruct a practical path around “Started with React Query”. Emphasis stays on contracts, checks, and drop-in code placeholders rather than motivational framing. When working through the Overview stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
Server state vs client state
The Server state vs client stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
Traditionally, this is how we fetch data
The Traditionally this is how stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(false)
const [error, setError] = useState(null)
useEffect(() => {
async function fetchUsers() {
try {
setLoading(true) const res = await fetch(
"https://jsonplaceholder.typicode.com/users"
) const data = await res.json() setUsers(data)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
} fetchUsers()
With React Query, the code above becomes:
The With React Query the stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
function Users() {
const {
data,
isLoading,
error
} = useQuery({
queryKey: ["users"],
queryFn: fetchUsers
})
if (isLoading) {
return <p>Loading...</p>
} if (error) {
return <p>Something went wrong</p>
} return (
<ul>
{data.map(user => (
<li key={user.id}>
{user.name}
</li>
))}
</ul>
)
}
The With React Query the stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
So, how do we actually use it?
For the So how do we stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
1. Setup
For the 1 Setup stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
npm install @tanstack/react-query
const queryClient = new QueryClient();
root.render(
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
2. Fetching data with useQuery
For the 2 Fetching data with stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see. For the 2 Fetching data with stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
const { data,
isPending,
error } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers
})
2.1. Query Function
When working through the 2 1 Query Function stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Treat effects as synchronization with the outside world, not as a substitute for derived values during render.
async function fetchUsers() {
const res = await fetch("/api/users")
if (!res.ok) {
throw new Error("Failed to fetch users")
}
return res.json()
}
useQuery({
queryKey: ["users"],
queryFn: fetchUsers
})
2.2. Query Key
When working through the 2 2 Query Key stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Treat effects as synchronization with the outside world, not as a substitute for derived values during render.
3. Caching
When working through the 3 Caching stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Treat effects as synchronization with the outside world, not as a substitute for derived values during render. When working through the 3 Caching stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
4. staleTime
The 4 staleTime stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
staleTime: 60,000 // 60 seconds
})
Why do we need staletime??
The Why do we need stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
Understanding staleTime
The Understanding staleTime stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs. The Understanding staleTime stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
User visits page
↓
Fetch users
↓
User navigates away
↓
User comes back
↓
Fetch users again
staleTime: 5 * 60 * 1000
5. gcTime
For the 5 gcTime stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
gcTime: 60000
})
What is the difference between staleTime and gcTime?
For the What is the difference stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
6. Refetching
For the 6 Refetching stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see. For the 6 Refetching stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
const { refetch } = useQuery(...)
refetch()
useQuery({
queryKey: ["users"],
queryFn: fetchUsers,
refetchInterval: 30,000
})
7. Mutations — Create, Update, Delete
When working through the 7 Mutations Create Update stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Treat effects as synchronization with the outside world, not as a substitute for derived values during render.
async function createUser(user) {
const response = await fetch(
"https://jsonplaceholder.typicode.com/users",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(user),
}
);
if (!response.ok) {
throw new Error("Failed to create user");
}
return response.json();
}
import { useMutation } from "@tanstack/react-query";
function CreateUser() {
const mutation = useMutation({
mutationFn: createUser,
});
return (
<button
onClick={() =>
mutation.mutate({
name: "John Doe",
email: "john@example.com",
})
}
disabled={mutation.isPending}
>
{mutation.isPending ? "Creating..." : "Create User"}
</button>
);
}
export default CreateUser;
Button click
↓
mutation.mutate(user)
↓
createUser(user)
↓
POST request
↓
Server
7.1. Mutation States
When working through the 7 1 Mutation States stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Treat effects as synchronization with the outside world, not as a substitute for derived values during render.
const {
mutate,
isPending,
isSuccess,
isError,
error,
data,
} = useMutation({
mutationFn: createUser,
});
<button
onClick={() => mutate({ userName: "delfina ghimire" })}
disabled={isPending}
>
{isPending ? "Creating..." : "Create user"}
</button>
8. Query Client
When working through the 8 Query Client stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Treat effects as synchronization with the outside world, not as a substitute for derived values during render. When working through the 8 Query Client stage, write down the contract first: required inputs, success signal, and what happens on partial failure. That checklist keeps later code changes honest. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
9. Query Invalidation
The 9 Query Invalidation stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
[
{ id: 1, userName: "delfina ghimire" },
{ id: 2, userName: "spiderman ghimire" },
]
mutation.mutate({
userName: "ironman ghimire",
});
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: createUser,
onSuccess: () => {
queryClient.invalidateQueries({
queryKey: ["users"],
});
},
});
Create Users (Mutation)
↓
Server data changes
↓
Invalidate ["users"]
↓
Query becomes stale
↓
Refetch
↓
UI gets fresh data
Invalidate vs. Refetch
The Invalidate vs Refetch stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Keep configuration outside application code. Environment files, secret stores, and feature flags belong in one place operators can audit without reading the whole graph. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs.
Conclusion
The Conclusion stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Document the happy path and the recovery path together. Retries, human gates, and dead-letter handling are part of the product, not later polish. Keep render work cheap and push expensive derivation behind memoization only after measuring. Premature memo can hide stale props bugs. The Conclusion stage works best when treated as a measurable surface. Capture one golden transcript, one failure case, and the rollback note before expanding scope. Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
TL;DR
For the TL DR stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state. Record timings and token or query cost next to functional results. Cost visibility early prevents surprise bills when the path moves from demo to shared environments. Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
Operational checklist
For the Operational checklist stage, define the inputs, the owner of the step, and the exit criteria before changing code. Operators should be able to re-run the step from a known checkpoint without guessing hidden state.
Prefer small, testable units over sprawling scripts. When a step fails, the failure should point at a single responsibility rather than a tangled pipeline.
Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
Write a short runbook: how to rotate keys, how to drain the queue, how to roll back the last ingest.
Treat this stage as a contract between inputs and validated outputs. Name the artifacts, define success checks, and refuse silent partial completion.
Colocate state with the component that owns the mutation. Lifting everything to a global store makes timing bugs harder to see.
Before promoting the stack, freeze versions, capture a golden transcript for the critical path, and confirm rollback steps. Shared environments need rate limits, tenancy checks, and a clear owner for secret rotation. Prefer boring reliability over clever one-off demos.
Batch note for 1aa45226c385: keep provider keys out of the repo, set a per-session token ceiling, and store transcripts next to the eval fixtures so later model swaps stay comparable.