Skip to content
Source
Sign inSign up

use-pull-to-refresh.ts

50 lines2.0 KB

1import { RegistryContext } from "@effect/atom-react";
2import { executeAtomQuery } from "@t3tools/client-runtime/state/runtime";
3import { type AsyncResult, type Atom } from "effect/reactivity";
4import { use, useEffect, useRef, useState } from "react";
5​
6export function usePullToRefresh<A, E>(atom: Atom.Atom<AsyncResult.AsyncResult<A, E>> | null) {
7 const registry = use(RegistryContext);
8 const request = useRef<{ atom: NonNullable<typeof atom>; controller: AbortController } | null>(
9 null,
10 );
11 const [refreshingAtom, setRefreshingAtom] = useState<typeof atom>(null);
12 if (refreshingAtom !== null && refreshingAtom !== atom) setRefreshingAtom(null);
13 // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- aborts the in-flight refresh for an atom when the atom changes or the component unmounts
14 useEffect(
15 () => () => {
16 if (request.current?.atom === atom) request.current.controller.abort();
17 },
18 [atom],
19 );
20​
21 const onRefresh = (beforeRefresh?: (signal: AbortSignal) => Promise<unknown>) => {
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 Promise.resolve()
29 .then(() => {
30 if (!controller.signal.aborted) return beforeRefresh?.(controller.signal);
31 })
32 .catch(() => undefined)
33 .then(() => {
34 if (controller.signal.aborted) return;
35 return executeAtomQuery(registry, atom, {
36 refresh: true,
37 signal: controller.signal,
38 reportFailure: false,
39 reportDefect: false,
40 });
41 })
42 .finally(() => {
43 if (controller.signal.aborted || request.current?.controller !== controller) return;
44 request.current = null;
45 setRefreshingAtom(null);
46 });
47 };
48​
49 return { refreshing: atom !== null && refreshingAtom === atom, onRefresh };