( options: UseActivityQueryOptions<T>, )
| 253 | * - Can refetch stale data when user becomes active again |
| 254 | */ |
| 255 | export function useActivityQuery<T>( |
| 256 | options: UseActivityQueryOptions<T>, |
| 257 | ): UseActivityQueryResult<T> { |
| 258 | const { |
| 259 | queryKey, |
| 260 | queryFn, |
| 261 | enabled = true, |
| 262 | staleTime = 0, |
| 263 | gcTime = 5 * 60 * 1000, |
| 264 | retry = 0, |
| 265 | refetchInterval = false, |
| 266 | refetchOnMount = false, |
| 267 | refetchOnActivity = false, |
| 268 | pauseWhenIdle = false, |
| 269 | idleThreshold = 30_000, |
| 270 | } = options |
| 271 | |
| 272 | const serializedKey = serializeQueryKey(queryKey) |
| 273 | const mountedRef = useRef(true) |
| 274 | const intervalRef = useRef<ReturnType<typeof setInterval> | null>(null) |
| 275 | const wasIdleRef = useRef(false) |
| 276 | |
| 277 | // Store queryFn in a ref to avoid recreating doFetch when queryFn changes. |
| 278 | // This is critical because inline arrow functions create new references on every render, |
| 279 | // which would cause the polling interval to reset constantly. |
| 280 | const queryFnRef = useRef(queryFn) |
| 281 | queryFnRef.current = queryFn |
| 282 | |
| 283 | // Snapshot includes entry + isFetching (so fetch-status updates rerender correctly) |
| 284 | const snap = useSyncExternalStore( |
| 285 | (cb) => subscribeToKey(serializedKey, cb), |
| 286 | () => getKeySnapshot<T>(serializedKey), |
| 287 | () => getKeySnapshot<T>(serializedKey), |
| 288 | ) |
| 289 | |
| 290 | const cachedEntry = snap.entry |
| 291 | const isFetching = snap.isFetching |
| 292 | |
| 293 | const data = cachedEntry?.data |
| 294 | const error = cachedEntry?.error ?? null |
| 295 | const dataUpdatedAt = cachedEntry?.dataUpdatedAt ?? 0 |
| 296 | |
| 297 | // Initial load = fetching with no successful data yet |
| 298 | const isLoading = isFetching && (cachedEntry == null || dataUpdatedAt === 0) |
| 299 | |
| 300 | const doFetch = useCallback(async (): Promise<void> => { |
| 301 | if (!enabled) return |
| 302 | |
| 303 | // global dedupe |
| 304 | const existing = inFlight.get(serializedKey) |
| 305 | if (existing) { |
| 306 | await existing |
| 307 | return |
| 308 | } |
| 309 | |
| 310 | const myGen = getGeneration(serializedKey) |
| 311 | setQueryFetching(serializedKey, true) |
| 312 |
no test coverage detected