| 1 | import { RegistryContext } from "@effect/atom-react"; |
| 2 | import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime"; |
| 3 | import { type AsyncResult, type Atom } from "effect/reactivity"; |
| 4 | import { use, useCallback, useEffect, useRef, useState } from "react"; |
| 5 | |
| 6 | /** Only a user's pull owns the refresh indicator; query invalidation stays silent. */ |
| 7 | export function usePullToRefresh<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>> | null) { |
| 8 | const registry = use(RegistryContext); |
| 9 | const request = useRef<{ atom: NonNullable<typeof atom>; controller: AbortController } | null>( |
| 10 | null, |
| 11 | ); |
| 12 | const [refreshingAtom, setRefreshingAtom] = useState<typeof atom>(null); |
| 13 | if (refreshingAtom !== null && refreshingAtom !== atom) setRefreshingAtom(null); |
| 14 | useEffect( |
| 15 | () => () => { |
| 16 | if (request.current?.atom === atom) request.current.controller.abort(); |
| 17 | }, |
| 18 | [atom], |
| 19 | ); |
| 20 | |
| 21 | const onRefresh = useCallback(() => { |
| 22 | if (!atom || (request.current?.atom === atom && !request.current.controller.signal.aborted)) |
| 23 | return; |
| 24 | request.current?.controller.abort(); |
| 25 | const controller = new AbortController(); |
| 26 | request.current = { atom, controller }; |
| 27 | setRefreshingAtom(atom); |
| 28 | void executeAtomQuery(registry, atom, { |
| 29 | refresh: true, |
| 30 | signal: controller.signal, |
| 31 | reportFailure: false, |
| 32 | reportDefect: false, |
| 33 | }).finally(() => { |
| 34 | if (controller.signal.aborted || request.current?.controller !== controller) return; |
| 35 | request.current = null; |
| 36 | setRefreshingAtom(null); |
| 37 | }); |
| 38 | }, [atom, registry]); |
| 39 | |
| 40 | return { refreshing: atom !== null && refreshingAtom === atom, onRefresh }; |
| 41 | } |