Skip to content

Instantly share code, notes, and snippets.

@enpitsuLin
Created June 18, 2024 05:57
Show Gist options
  • Save enpitsuLin/b4f70e8d4b83cfe37f253cc7f95292ea to your computer and use it in GitHub Desktop.
Save enpitsuLin/b4f70e8d4b83cfe37f253cc7f95292ea to your computer and use it in GitHub Desktop.
simple react-query
const __internalStore = new Map<string, any>()
const __listeners = new Set<() => void>()
const cache = {
set(key: string, value: any) {
__internalStore.set(key, value);
__listeners.forEach(listener => listener());
},
delete(key: string) {
__internalStore.delete(key);
__listeners.forEach(listener => listener());
},
subscribe(listener: () => void) {
__listeners.add(listener);
return () => __listeners.delete(listener);
},
get<T>(key: string): T {
return __internalStore.get(key);
}
}
interface QueryOptions<Result = any> {
queryKey: unknown[],
queryFn: () => Promise<Result>
initialData?: Result
}
interface UseQueryReturnBase<Result = any> {
isPending: boolean
isError: boolean,
data: Result | null
error: unknown | null
refresh: () => void
}
interface UseQueryReturnPending<Result = any> extends UseQueryReturnBase<Result> {
isPending: true
isError: false,
data: Result | null
error: null
refresh: () => void
}
interface UseQueryReturnSuccess<Result = any> extends UseQueryReturnBase<Result> {
isPending: false
isError: false
data: Result
error: null
refresh: () => void
}
interface UseQueryReturnError<Result = any> extends UseQueryReturnBase<Result> {
isPending: false
isError: true,
data: Result | null
error: unknown
refresh: () => void
}
type UseQueryReturn<Result = any> = UseQueryReturnPending<Result> | UseQueryReturnSuccess<Result> | UseQueryReturnError<Result>
function useQuery<Result = any>(options: QueryOptions<Result>): UseQueryReturn<Result> {
const _options = useMemo(() => options, [options.queryKey])
const {
queryKey,
queryFn,
initialData: defaultData
} = _options
const key = queryKey.map(i => JSON.stringify(i)).join(':')
const data = useSyncExternalStore(
cache.subscribe,
useCallback(
() => cache.get<Result>(key) ?? defaultData ?? null,
[_options]
)
);
const [isPending, setIsPending] = useState(false)
const [error, setError] = useState<unknown | null>(null)
const isCancelled = useRef(false)
function refresh() {
isCancelled.current = false;
setIsPending(true);
queryFn()
.then(data => {
if (!isCancelled.current) {
cache.set(key, data);
setIsPending(false);
}
})
.catch(err => {
setError(err)
})
.finally(() => {
cleanupEffect()
});
}
function cleanupEffect() {
isCancelled.current = true;
setIsPending(false);
}
useEffect(() => {
refresh()
return cleanupEffect
}, [key])
return {
isPending,
isError: !!error,
data,
error,
refresh
} as UseQueryReturn<Result>
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment