This episode teaches techniques to make the application feel instant: prefetchQuery to seed data into the cache before render, and initialData and placeholderData for instant rendering from already-available data, including data from another query's cache.

One of the most effective ways to improve the quality of the user experience isn't making the fetch faster, but making the fetch imperceptible. If the data is already available before the user needs it, the loading screen never appears. This is the essence of prefetching and data seeding.
Episode 10 covers three tools: prefetchQuery to seed data into the cache, initialData to fill in initial data, and placeholderData to display temporary data. All three use already-available data to eliminate wait time.
prefetchQuery fetches data and stores it in the cache without waiting for the component to mount. Ideally it is called in an event handler — for example when the user hovers over a link:
import { useQueryClient } from "@tanstack/react-query"
function TodoRow({ todo }) {
const queryClient = useQueryClient()
return (
<li
onMouseEnter={() =>
queryClient.prefetchQuery({
queryKey: ["todos", todo.id],
queryFn: () => fetchTodo(todo.id),
})
}
>
{todo.title}
</li>
)
}When the mouse touches a todo row, the todo's detail data is prefetched into the cache. queryClient.prefetchQuery accepts the same configuration as useQuery — queryKey and queryFn — then stores the result. When the user finally opens the detail page, the data is already there.
The same pattern can be triggered before page navigation — for example in the "Next" click handler of pagination. The next page's data is already warm before render, so the transition feels instant:
onClick={() => {
queryClient.prefetchQuery({
queryKey: ["todos", "page", page + 1],
queryFn: () => fetchTodos(page + 1),
})
setPage((p) => p + 1)
}}prefetchQuery writes to the same cache that useQuery will read. If the fetch fails, the prefetch is discarded without an error that bothers the user — prefetch is best-effort.
initialData fills the cache with data you already have, for example data passed from the server or from another state. The query is immediately considered successful without showing a loading state:
const { data } = useQuery({
queryKey: ["todos"],
queryFn: fetchTodos,
initialData: seedTodos,
})initialData: seedTodos makes the query display seedTodos instantly, without a loading state. initialData is treated as real data (not a placeholder), so isLoading is never true and isError never appears — a feature to be aware of because errors are hidden behind the initial data.
One of the smartest uses of initialData: using another query's data that contains the same data. The classic example, a todo detail seeded from the todo list data:
const { data } = useQuery({
queryKey: ["todos", id],
queryFn: () => fetchTodo(id),
initialData: () =>
queryClient.getQueryData(["todos"])?.find((t) => t.id === id),
})initialData accepts a function that returns data from another cache. queryClient.getQueryData(["todos"]) reads the already-existing list, then .find grabs the needed item. The result: the detail page shows instant data and refreshes in the background.
These two options are often confused. The difference matters:
initialData → real data: fills the cache, no loading, no isFetching
placeholderData → fake data: displays temporarily, loading status keeps runningThe summary initialData → real data: fills the cache emphasizes that initialData really writes to the cache. placeholderData only displays temporary data while a fetch runs in the background — the isFetching status stays true so an indicator can still be shown.
keepPreviousData in episode 9 — and you still want the latest fetch to run.Because initialData marks the query as successful, data can show stale information without being noticed. Consider an appropriate staleTime, or let a background refetch run soon. If you only need displayed data without writing the cache, placeholderData is usually the safer choice.
Tip
The best combination: prefetch in an event handler to prepare the cache earlier, then let the normal useQuery read the already-warm cache. initialData should be used for data that is already in your hands, not to imitate prefetching.
Episode 10 gave you three ways to seed data so the application feels instant: prefetchQuery to prepare the cache early, initialData to fill the cache with valid data, and placeholderData to display temporary data.
Key takeaways:
prefetchQuery writes the cache without waiting for a component render.initialData fills the cache with real data without a loading state.placeholderData displays temporary data while a fetch runs.initialData.initialData marks the query as successful, so watch out for data staleness.In the next episode, episode 11, we will discuss optimistic updates — updating the UI before the server responds with onMutate and setQueryData, then automatic rollback in onError. This is a key pattern for features like likes and toggles that feel instant.