"Using useEffect for data fetching is the most common self-inflicted wound in React development. Stop it."
Fetching data is easy.
Handling loading states, error states, caching, deduping, revalidation, focus-refetching, and pagination is hard.
React Query removes the need for useEffect entirely.
Server State (remote, shared, async) is different from Client State (local, synchronous). Treat them differently.
02. Stale Time vs Cache Time
This confuses 99% of developers. Here is the definitive explanation.
⏱️ Stale Time
"How long until I consider this data 'old' and should refetch?"
Default: 0 (Immediately refetch on mount).
🗑️ Cache Time
"How long until I delete the data from memory entirely?"
Default: 5 minutes.
If staleTime is 5 minutes, React Query won't refetch even if you mount the component 100 times. It serves from cache instantly.
03. Optimistic Updates (The Magic)
Update the UI immediately when the user clicks. Don't wait for the server. If it fails, rollback. This makes your app feel instant, like a native iOS app.
const mutation = useMutation({
mutationFn: newTodo => axios.post('/todos', newTodo),
// When mutate is called:
onMutate: async (newTodo) => {
await queryClient.cancelQueries(['todos'])
const previousTodos = queryClient.getQueryData(['todos'])
// Optimistically update to the new value
queryClient.setQueryData(['todos'], old => [...old, newTodo])
return { previousTodos }
},
onError: (err, newTodo, context) => {
// Rollback ONLY if error
queryClient.setQueryData(['todos'], context.previousTodos)
},
})
04. Infinite Queries
Pagination is boring. Infinite scroll is where it's at.
useInfiniteQuery handles the complexity of `nextPageParam`, merging pages, and bidirectional fetching for you.
06. Fetch Visualizer
Simulate network latency and see how optimistic updates trick the user into thinking the app is instant.
Concept Optimistic UI Mode
Toggle "Optimistic Mode" inside the demo. When ON, the list updates instantly. When OFF, it waits for the "Server" (1.5s delay).