-
-
Notifications
You must be signed in to change notification settings - Fork 3.6k
RFC: Component-level Visibility-based Query Management #9934
-
RFC: Component-level Visibility-based Query Management
Implementation
Problem
refetchOnWindowFocus works well, but it's window-scoped.
In practice, I often need finer control:
// MFE with multiple QueryClients <QueryClientProvider client={clientA}> <SectionA /> </QueryClientProvider> <QueryClientProvider client={clientB}> <SectionB /> </QueryClientProvider> // Only the active tab should refresh <Tabs> <TabPanel id="analytics"><AnalyticsPanel /></TabPanel> <TabPanel id="users"><UsersPanel /></TabPanel> </Tabs> // Widgets below the fold shouldn't fetch until scrolled into view <Dashboard> <RevenueWidget /> {/* visible */} <OrdersWidget /> {/* not visible yet */} </Dashboard>
Doing this manually means setting up IntersectionObserver, tracking state, coordinating invalidation... it adds up.
Proposal
Visibility-based query management at the component level.
Hook
function Widget() { const { ref, isFocused } = useComponentFocus({ componentKey: 'revenue', invalidateOnFocus: true, }) const { data } = useQuery({ queryKey: ['revenue'], queryFn: fetchRevenue }) return <div ref={ref}>{data}</div> }
function Dashboard() { const { registerComponent, focusGroup, blurGroup } = useComponentFocusGroup({ groupKey: 'dashboard', componentKeys: ['a', 'b'], }) return ( <> <button onClick={focusGroup}>Refresh All</button> <div ref={registerComponent('a')}><WidgetA /></div> <div ref={registerComponent('b')}><WidgetB /></div> </> ) }
Component
<ComponentFocus componentKey="revenue" invalidateOnFocus> <RevenueChart /> </ComponentFocus>
<ComponentFocusGroup groupKey="dashboard" componentKeys={['a', 'b']}> {({ registerComponent, focusGroup }) => ( <> <button onClick={focusGroup}>Refresh All</button> <div ref={registerComponent('a')}><WidgetA /></div> <div ref={registerComponent('b')}><WidgetB /></div> </> )} </ComponentFocusGroup>
How it works
- Uses IntersectionObserver to detect visibility
- Invalidates/refetches queries when component enters viewport
- Respects tab switching and window blur
- Works with multiple QueryClient instances
Use cases
Third-party Widget Integration
<QueryClientProvider client={appClient}> <App /> </QueryClientProvider> <QueryClientProvider client={widgetClient}> <AnalyticsWidget /> {/* loads data only when scrolled into view */} </QueryClientProvider>
Isolated Feature Modules
<QueryClientProvider client={paymentClient}> <PaymentSection /> {/* separate cache policy for sensitive data */} </QueryClientProvider> <QueryClientProvider client={mainClient}> <ContentSection /> </QueryClientProvider>
MFE Portal
<QueryClientProvider client={teamAClient}> <Header /> </QueryClientProvider> <QueryClientProvider client={teamBClient}> <Sidebar /> </QueryClientProvider> <QueryClientProvider client={teamCClient}> <MainContent /> </QueryClientProvider>
On window focus, only visible sections refetch — each with their own QueryClient.
Open questions
- Useful enough to add as experimental?
- Separate package or integrated into react-query?
Thoughts?
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 2 comments
-
correct me if I’m wrong but I don’t think this has a lot to do with react-query - it’s more about visibility management and then one consumer of that would be refetching with react-query?
Probably a better design for this lib would be to just expose a function that will be called whenever something becomes visible, and then users can do with that information what they want instead of having query specific options like, invalidateOnFocus, refetchOnFocus, resetOnFocus etc.
I don’t think this has a place in the query repo but it sounds like a nice package you could publish.
Beta Was this translation helpful? Give feedback.
All reactions
-
@TkDodo Thanks for the feedback. Visibility management is a concern, but not the only one.
One thing I'm thinking about though: in setups where modules have separate QueryClient instances, each module usually has its own QueryClientProvider.
If we only expose an onFocusChange callback, each module would need to access its own QueryClient (via useQueryClient()), define its invalidation logic, and pass that callback up to the parent. That means either the parent needs to know about each module's query structure, or modules need to expose their handlers — which feels like it increases coupling.
What do you think?
Beta Was this translation helpful? Give feedback.