Download frontend/src/lib/hooks.ts from omaib/MuSProt: direct link, hf CLI and curl.
- Browser
- Download file 2.57 kB
-
https://huggingface.co/spaces/omaib/MuSProt/resolve/main/frontend/src/lib/hooks.ts
- Command line
-
hf download hf://spaces/omaib/MuSProt/frontend/src/lib/hooks.ts
-
curl -L -o hooks.ts https://huggingface.co/spaces/omaib/MuSProt/resolve/main/frontend/src/lib/hooks.ts
2.57 kB
| import { useEffect, useRef, useState } from 'react'; | |
| export interface AsyncState<T> { | |
| data: T | null; | |
| error: Error | null; | |
| loading: boolean; | |
| } | |
| const cache = new Map<string, unknown>(); | |
| /** | |
| * Fetch data keyed by `key`; aborts stale requests and memoises successful | |
| * responses for the session so back/forward navigation is instant. | |
| */ | |
| export function useApi<T>(key: string | null, fetcher: (signal: AbortSignal) => Promise<T>): AsyncState<T> { | |
| const cached = key ? (cache.get(key) as T | undefined) : undefined; | |
| const [state, setState] = useState<AsyncState<T>>({ | |
| data: cached ?? null, | |
| error: null, | |
| loading: key !== null && cached === undefined, | |
| }); | |
| const fetcherRef = useRef(fetcher); | |
| fetcherRef.current = fetcher; | |
| useEffect(() => { | |
| if (key === null) { | |
| setState({ data: null, error: null, loading: false }); | |
| return; | |
| } | |
| const hit = cache.get(key) as T | undefined; | |
| if (hit !== undefined) { | |
| setState({ data: hit, error: null, loading: false }); | |
| return; | |
| } | |
| const controller = new AbortController(); | |
| setState((s) => ({ data: s.data, error: null, loading: true })); | |
| fetcherRef | |
| .current(controller.signal) | |
| .then((data) => { | |
| cache.set(key, data); | |
| if (!controller.signal.aborted) setState({ data, error: null, loading: false }); | |
| }) | |
| .catch((error: Error) => { | |
| if (!controller.signal.aborted) setState({ data: null, error, loading: false }); | |
| }); | |
| return () => controller.abort(); | |
| }, [key]); | |
| return state; | |
| } | |
| /** Warm the useApi cache for a key (e.g. the next thing a user is likely to open). */ | |
| export function prefetch<T>(key: string, fetcher: () => Promise<T>): void { | |
| if (cache.has(key)) return; | |
| fetcher() | |
| .then((data) => cache.set(key, data)) | |
| .catch(() => { | |
| /* best effort */ | |
| }); | |
| } | |
| /** Resolve a best-effort promise (e.g. an RCSB title) into state. */ | |
| export function usePromise<T>(factory: () => Promise<T> | null, deps: unknown[]): T | null { | |
| const [value, setValue] = useState<T | null>(null); | |
| useEffect(() => { | |
| let alive = true; | |
| setValue(null); | |
| const p = factory(); | |
| p?.then((v) => alive && setValue(v)); | |
| return () => { | |
| alive = false; | |
| }; | |
| // eslint-disable-next-line react-hooks/exhaustive-deps | |
| }, deps); | |
| return value; | |
| } | |
| export function useDebounced<T>(value: T, ms: number): T { | |
| const [v, setV] = useState(value); | |
| useEffect(() => { | |
| const t = setTimeout(() => setV(value), ms); | |
| return () => clearTimeout(t); | |
| }, [value, ms]); | |
| return v; | |
| } | |