@apparently/query
Version:
Simple and small data fetching library for SolidJS. Inspired by SWR.
128 lines (127 loc) • 4.68 kB
JSX
import { createContext, onMount, onCleanup, useContext, createResource } from "solid-js";
import { ReactiveMap } from "@solid-primitives/map";
// @ts-ignore
import stableHash from "stable-hash";
const QueryContext = createContext();
class ReactiveMapWithStableHash extends ReactiveMap {
set(key, value) {
if (stableHash(super.get(key)) === stableHash(value)) {
return this;
}
return super.set(key, value);
}
}
// TODO:
// - deduplikace
// - infiniteQuery (v postupu)
// - error retry
// - isRefething
// - refetch on reconnect
export function useQueryContext() {
return useContext(QueryContext);
}
export const QueryConfig = (props) => {
const cache = new ReactiveMapWithStableHash(); // Tady možná je trochu overheat, že has atd je reaktivní.
return (<QueryContext.Provider value={{ fetcher: props.fetcher, cache }}>
{props.children}
</QueryContext.Provider>);
};
export function useCache() {
const { cache } = useQueryContext();
return cache;
}
export function useMutate() {
const { fetcher, cache } = useQueryContext();
return async (key, data) => { cache.set(key, data ? data : await fetcher(key)); };
}
export function useQuery(getKey, initialValue) {
const { fetcher, cache } = useQueryContext();
const globalMutate = useMutate();
const mutate = (data) => globalMutate(getKey(), data);
const refetch = () => mutate();
const cacheStorage = (initialValue) => {
const key = getKey();
if (!cache.has(key) && initialValue) {
cache.set(key, initialValue);
}
return [
() => cache.get(key),
(newValue) => {
cache.set(key, newValue());
return cache.get(key);
}
];
};
const cacheFetcher = async (key) => {
if (cache.has(key)) {
(async () => cache.set(key, await fetcher(key)))();
return cache.get(key);
}
return await fetcher(key);
};
// @ts-ignore
// TODO: opravit type problem.
const [resource] = createResource(getKey, cacheFetcher, { storage: cacheStorage, initialValue });
if (typeof window !== "undefined") {
onMount(() => window.addEventListener("focus", refetch));
onCleanup(() => window.removeEventListener("focus", refetch));
}
return [resource, { mutate }];
}
// TODO: caching, suspense, klasický nekonečný scroll (bez deloadingu), vyčistit kód...
//type UseInfiniteQueryGetKey<T> = (pageIndex: number, previousPageData: T | null) => string;
//type UseInfiniteQuery<T> = [InitializedResource<T[][]>, { setSize: Setter<number>, size: Accessor<number>, loadingNextPage: Accessor<boolean> }];
// export function useInfiniteQuery<T>(getKey: UseInfiniteQueryGetKey<T>, initialValue?: T[]) {
// const { fetcher, cache } = useQueryContext();
// const [size, setSize] = createSignal(0);
// const [loadingPage, setLoadingPage] = createSignal(false);
// const [data, setData] = createSignal<T[]>([]);
// let max = 0;
// onMount(async () => setData(await fetcher(getKey(0, null))));
// createEffect(() => {
// if (data().length > max) {
// max = data().length;
// }
// const _size = untrack(size);
// console.log("Max loaded sofar:", max, "Size:", _size);
// });
// async function forward() {
// if (loadingPage()) {
// return;
// }
// setLoadingPage(true);
// let _data = untrack(data);
// const newSize = size() + 1;
// const fetchedData = await fetcher(getKey(newSize, null));
// let newData = [..._data, ...fetchedData];
// if (newSize % 4 == 0) {
// newData = newData.slice(newData.length - 200, newData.length);
// }
// setData(newData);
// setSize(newSize);
// setLoadingPage(false);
// }
// async function back() {
// if (loadingPage()) {
// return;
// }
// if (size() <= 3) {
// if (size() != 0) {
// setSize(0);
// }
// return;
// }
// setLoadingPage(true);
// let _data = untrack(data);
// const newSize = size() - 1;
// const fetchedData = await fetcher(getKey(newSize - 3, null));
// let newData = [...fetchedData, ..._data];
// if (newSize % 4 == 0) {
// newData = newData.slice(0, newData.length - 200);
// }
// setData(newData);
// setSize(newSize);
// setLoadingPage(false);
// }
// return [data, { back, forward }];
// }