import { useEffect, useState, useRef } from "react"; import { Query, QueryKey, useQueryClient } from "@tanstack/react-query"; /** * Custom hook to track a single query by its queryKey with live updates * Optimized to only re-render when the specific query changes */ interface QueryWithVersion { query: Query | undefined; version: number; } /** * Returns a single query instance matching the provided key and resubscribes whenever the cache * entry changes. Ideal for detail panes where live updates are required without scanning the * entire query cache. */ export function useGetQueryByQueryKey(queryKey?: QueryKey): Query | undefined { const { query } = useGetQueryByQueryKeyWithVersion(queryKey); return query; } /** * Returns a single query instance with a version number that increments on each cache update. * Use the version as a dependency or key to ensure child components re-render when query state changes. * * This is necessary because React Query mutates the Query object in place, so React's * shallow comparison won't detect changes to nested properties like query.state.status. */ export function useGetQueryByQueryKeyWithVersion(queryKey?: QueryKey): QueryWithVersion { const queryClient = useQueryClient(); const [queryState, setQueryState] = useState({ query: undefined, version: 0, }); const queryHashRef = useRef(undefined); useEffect(() => { if (!queryKey) { setQueryState({ query: undefined, version: 0 }); queryHashRef.current = undefined; return; } // Get initial query state const query = queryClient.getQueryCache().find({ queryKey, exact: true }); setQueryState({ query, version: 0 }); // Store the stringified queryKey for comparison const queryKeyString = JSON.stringify(queryKey); queryHashRef.current = queryKeyString; // Subscribe to query cache changes but only update if our specific query changed const unsubscribe = queryClient.getQueryCache().subscribe((event) => { // Only process events for our specific query if ( event.type === "updated" || event.type === "added" || event.type === "removed" ) { if ("query" in event && event.query) { // Check if the event is for our query by comparing the stringified keys const eventQueryKeyString = JSON.stringify(event.query.queryKey); const isOurQuery = eventQueryKeyString === queryHashRef.current; if (isOurQuery) { if (event.type === "removed") { setQueryState({ query: undefined, version: 0 }); } else { // For 'updated' and 'added' events, use the query from the event // Update both the query and increment version to force re-renders setQueryState((prev) => ({ query: event.query, version: prev.version + 1, })); } } } } }); // Cleanup subscription when component unmounts return () => unsubscribe(); }, [queryClient, queryKey]); return queryState; }