4.6 KiB
4.6 KiB
cache-placeholder-vs-initial: Understand Placeholder vs Initial Data
Priority: MEDIUM
Explanation
placeholderData and initialData both provide data before the fetch completes, but behave differently. initialData is treated as real cached data, while placeholderData is temporary and doesn't persist to cache. Choose based on whether your fallback data should be cached.
Bad Example
// Using initialData when you don't want it cached
function PostPreview({ postId, previewData }: Props) {
const { data } = useQuery({
queryKey: ['posts', postId],
queryFn: () => fetchPost(postId),
initialData: previewData, // Wrong: this becomes cached "truth"
// If previewData is incomplete, it pollutes the cache
// staleTime applies to this data as if it were fetched
})
}
// Using placeholderData when you want persistence
function UserProfile({ userId }: Props) {
const { data } = useQuery({
queryKey: ['users', userId],
queryFn: () => fetchUser(userId),
placeholderData: cachedUserFromList, // Wrong: won't persist
// User navigates away and back - placeholder shown again
// No cache entry created until fetch completes
})
}
Good Example: placeholderData for Temporary Display
// Show list data while fetching detail
function PostDetail({ postId }: { postId: string }) {
const queryClient = useQueryClient()
const { data, isPlaceholderData } = useQuery({
queryKey: ['posts', postId],
queryFn: () => fetchPost(postId),
placeholderData: () => {
// Use partial data from list cache as placeholder
const posts = queryClient.getQueryData<Post[]>(['posts'])
return posts?.find(p => p.id === postId)
},
})
return (
<article className={isPlaceholderData ? 'opacity-50' : ''}>
<h1>{data?.title}</h1>
{isPlaceholderData ? (
<p>Loading full content...</p>
) : (
<div>{data?.content}</div>
)}
</article>
)
}
Good Example: initialData for Known Good Data
// SSR: Data fetched on server should be initial
function PostPage({ serverData }: { serverData: Post }) {
const { data } = useQuery({
queryKey: ['posts', serverData.id],
queryFn: () => fetchPost(serverData.id),
initialData: serverData,
// Specify when this data was fetched for proper stale calculation
initialDataUpdatedAt: serverData.fetchedAt,
})
return <PostContent post={data} />
}
// Pre-seeding cache with complete data
function App() {
const queryClient = useQueryClient()
// If you have complete, authoritative data
useEffect(() => {
queryClient.setQueryData(['config'], completeConfigData)
}, [])
}
Good Example: keepPreviousData Pattern
// Keep showing old data while fetching new (pagination, filters)
function ProductList({ page }: { page: number }) {
const { data, isPlaceholderData } = useQuery({
queryKey: ['products', page],
queryFn: () => fetchProducts(page),
placeholderData: keepPreviousData, // Built-in helper
})
return (
<div className={isPlaceholderData ? 'opacity-70' : ''}>
{data?.map(product => (
<ProductCard key={product.id} product={product} />
))}
{isPlaceholderData && <LoadingOverlay />}
</div>
)
}
Comparison Table
| Behavior | initialData |
placeholderData |
|---|---|---|
| Persisted to cache | Yes | No |
staleTime applies |
Yes | No (always fetches) |
isPlaceholderData |
false |
true |
| Shown to other components | Yes (cached) | No |
| Use case | SSR, complete known data | Preview, previous page |
Affects dataUpdatedAt |
Yes (use initialDataUpdatedAt) |
No |
Good Example: Combining Both
function PostDetail({ postId, ssrData }: Props) {
const queryClient = useQueryClient()
const { data } = useQuery({
queryKey: ['posts', postId],
queryFn: () => fetchPost(postId),
// If we have SSR data, use as initial (cached)
initialData: ssrData,
initialDataUpdatedAt: ssrData?.fetchedAt,
// If no SSR data, try to use list preview as placeholder
placeholderData: () => {
if (ssrData) return undefined // Already have initial
const posts = queryClient.getQueryData<Post[]>(['posts'])
return posts?.find(p => p.id === postId)
},
})
}
Context
placeholderDatacan be a value or function (lazy evaluation)initialDataaffects cache immediately on query creation- Use
initialDataUpdatedAtwithinitialDatafor proper stale calculations keepPreviousDatais a built-in placeholder strategy- Check
isPlaceholderDatato show loading indicators placeholderDatais ideal for "instant" UI while fetching