React & State · T190

How should Zustand and TanStack Query work together?

Keep a shared selected ID in Zustand and fetch the product through TanStack Query. Include that ID in the query key instead of duplicating the product snapshot.

The important bit
After saving, invalidate the matching query so active readers can refetch. If selection is local, useState may be enough. Demo: Zustand 5.0.8 and Query 5.90.3.

Understand it. Then fix it.

Store the selection. Fetch the product.

Keep the selected identifier in Zustand, and the fetched product in Query. Store the pointer, not a second server snapshot.

const useSelection = create(() => ({
  selectedId: 1
}));

useSelection.setState({
  selectedId: 2
});

Selection drives the query key.

Read that identifier with a selector, then include it in the query key. Changing one to two makes this component observe product two's cache entry.

const id = useSelection(
  s => s.selectedId
 );
const product = useQuery({
  queryKey: ["product", id],
  queryFn: () => getProduct(id)
});

Refresh one source of truth.

After saving a price, invalidate the matching product query. Active readers can refetch. You do not also need to patch a product copy in Zustand.

await savePrice(id, price);
await queryClient.invalidateQueries({
  queryKey: ["product", id]
});

One reader? Keep it local.

If only one component needs the selection, useState may be enough. Zustand earns its place when that client selection really needs sharing.

const [id, setId] = useState(1);

Code blocks are teaching excerpts. Keep the surrounding error handling and application requirements.

Save the code excerpts ↓
Read the full transcript

How should Zustand and TanStack Query work together? My selected product has two prices, which feels ambitious for one product. Keep the selected identifier in Zustand, and the fetched product in Query. Store the pointer, not a second server snapshot. Read that identifier with a selector, then include it in the query key. Changing one to two makes this component observe product two's cache entry. After saving a price, invalidate the matching product query. Active readers can refetch. You do not also need to patch a product copy in Zustand. If only one component needs the selection, useState may be enough. Zustand earns its place when that client selection really needs sharing. Two prices for one product. We accidentally invented enterprise billing.

Go to the source