Back to Debug Dungeon

TanStack Query: Cache Invalidation Mismatch

1/13/2026

🐛 The Bug

You click the edit button to update a user's profile. The API request succeeds, you see the success toast,onSuccess fires... but the UI doesn't update.

You refresh the page manually and the updated data is there. So what gives?

🔍 The Code

You have two components: one fetches and displays user data, the other updates it.

Component A - Displaying Data:

// UserProfile.tsx
import { useParams } from 'react-router-dom';
import { useQuery } from '@tanstack/react-query';

function UserProfile() {
	const { userId } = useParams<{ userId: string }>();
	
	const { data: user } = useQuery({
		queryKey: ['user', userId],
		queryFn: () => fetchUser(userId),
	});
	
	return (
		<div>
			<h1>{user?.name}</h1>
			<p>{user?.email}</p>
			<EditUserModal userId={userId} />
		</div>
	);
}

Component B - Updating Data:

// EditUserModal.tsx
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';

interface EditUserModalProps {
	userId: string;
}

function EditUserModal({ userId }: EditUserModalProps) {
	const queryClient = useQueryClient();
	
	// Query user from database - returns userId as a number
	const { data: userFromDb } = useQuery({
		queryKey: ['user', userId],
		queryFn: () => fetchUser(userId),
	});
	
	const { mutate } = useMutation({
		mutationFn: (updates: UserUpdate) => updateUser(userFromDb.id, updates),
		onSuccess: () => {
			toast.success('Profile updated!');
			queryClient.invalidateQueries({
				queryKey: ['user', userFromDb.id],
			});
		},
	});
	
	return (
		<button onClick={() => mutate({ name: 'New Name' })}>
			Update Profile
		</button>
	);
}

The Problem: Toast shows success, API call works, but the UI shows stale data until you manually refresh.

💡 Hints

Other Ways This Can Appear

Boolean vs String

['items', true] vs ['items', 'true']

Undefined vs Null

['user', undefined] vs ['user', null]

Array Order

['posts', userId, postId] vs ['posts', postId, userId]

Object References

Different object instances with same properties = different keys

Quick Fix

When in doubt, just stringify everything in your query keys. Way easier to stay consistent with strings than to remember which IDs are numbers vs strings.

Type-Safe Routing

Libraries like TanStack Router actually solve this at the routing level with proper TypeScript inference for URL params. Your params get the right types from the start instead of everything being strings.

Worth considering if you're starting fresh or this is a common issue you encounter.