# 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 ```tsx // 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 ```tsx // 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(['posts']) return posts?.find(p => p.id === postId) }, }) return (

{data?.title}

{isPlaceholderData ? (

Loading full content...

) : (
{data?.content}
)}
) } ``` ## Good Example: initialData for Known Good Data ```tsx // 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 } // 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 ```tsx // 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 (
{data?.map(product => ( ))} {isPlaceholderData && }
) } ``` ## 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 ```tsx 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(['posts']) return posts?.find(p => p.id === postId) }, }) } ``` ## Context - `placeholderData` can be a value or function (lazy evaluation) - `initialData` affects cache immediately on query creation - Use `initialDataUpdatedAt` with `initialData` for proper stale calculations - `keepPreviousData` is a built-in placeholder strategy - Check `isPlaceholderData` to show loading indicators - `placeholderData` is ideal for "instant" UI while fetching