Challenge: Multi-tab Sync Dashboard
Learn how to build a small dashboard where two tabs share the same server state, handle writes, and stay consistent without refetching everything.
We'll cover the following...
Problem statement
We are building a multi-tab dashboard that renders two independent app instances, Tab A and Tab B, on the same page to simulate two browser tabs. Each tab uses its own QueryClient, which means each tab maintains an isolated cache and can drift out of sync after a write.
When a write happens in one tab, that tab should broadcast an event. The other tab listens for that event and converges by invalidating only the queries whose server truth may have changed. This should be selective invalidation, not “invalidate everything,” and refetching should happen only for active or visible queries to avoid unnecessary network and UI churn.
Success criteria
With the architecture in place, the following success criteria confirm that the dashboard behaves like a production app and that cache coordination works correctly across both tabs.
Tab AandTab Beach has its own cache but views the same server data.Selecting an account triggers dependent queries (details + activity feed).
The activity feed uses infinite queries and can load more pages.
“Pin” toggling is an optimistic mutation with rollback on error.
After a mutation, we perform selective invalidation (not global refetch).
A mutation in one tab invalidates the other tab selectively via a
BroadcastChannel.Conditional refetching: when a panel is hidden/unmounted, invalidation should not cause visible churn; it should refetch when remounted.
Technical requirements: Implement the following features step by step:
Task 1: Two independent tabs (two caches)
Create two separateQueryClientinstances and render two dashboard panels,Tab AandTab B. Each tab should run as an independent app instance with its own cache, while both read from the same server functions.Task 2: Cache-shaped reads
Add an["accounts"]list query and renders the accounts in each tab. Selecting an account should update the local selection state per tab.Tab AandTab Bcan select different accounts without affecting each other.Task 3: Dependent queries
When an account is selected, enable two account-scoped queries:["account", accountId]for account summary/details["activity", accountId]as an infinite query for the activity feed
These queries should be disabled untilaccountIdis available.
Task 4: Infinite queries and pagination
Render activity items fromdata.pagesand flatten the results for display. Add aLoad morebutton that callsfetchNextPage()and disables itself while the next page is loading.Task 5: Mutations, optimistic updates, selective invalidation, and cross-tab sync
Add aPintoggle for activity items with the following behavior:Apply an optimistic update to the cached
["activity", accountId]pages so the UI updates immediatelyRoll back the cache if the mutation fails
After success, invalidate only
["account", accountId]to refresh derived fields (such as pinned counts)Broadcast a message so the other tab selectively invalidates only the affected query keys, not the entire cache
Project structure
Below is the hierarchy of the project files and folders: