UNPKG

valync

Version:

**A lightweight, framework-agnostic async data handling library for React & Vue, inspired by Riverpod’s AsyncValue pattern and powered by ts-results-es.**

212 lines 7.68 kB
import { useEffect, useRef, useState } from "react"; import { Some, None } from "ts-results-es"; import { normalizeKey, AsyncLoading, AsyncError, AsyncData, } from "../core"; const cache = new Map(); export function createValyn({ client, }) { return function (key, options = {}) { const keyStr = normalizeKey(key); const controllerRef = useRef(null); const [state, setState] = useState(() => { if (options.initialData) { return options.initialData.status === "success" ? new AsyncData(Some(options.initialData.data)) : new AsyncError(options.initialData.error); } if (options.cache !== false && cache.has(keyStr)) { return cache.get(keyStr); } return new AsyncData(None); }); const isClient = typeof window !== "undefined" && typeof AbortController !== "undefined"; const doFetch = () => { controllerRef.current?.abort(); const ctrl = new AbortController(); controllerRef.current = ctrl; if (options.cache !== false && cache.has(keyStr)) { setState(cache.get(keyStr)); return; } setState(new AsyncLoading()); const attempt = (tries) => { client(typeof key === "string" ? key : keyStr, { ...options.init, signal: ctrl.signal, }) .then((res) => { if (ctrl.signal.aborted) return; if (res.status == "failed") { setState(new AsyncError(res.error)); return; } const data = options.onData ? options.onData(res.data) : res.data; const sd = new AsyncData(Some(data)); if (options.cache !== false) cache.set(keyStr, sd); setState(sd); }) .catch((err) => { if (ctrl.signal.aborted) return; if (tries > 0) return attempt(tries - 1); setState(new AsyncError({ name: "NetworkError", message: err.message, })); }); }; attempt(options.retryCount ?? 0); }; useEffect(() => { if (!isClient || options.initialData) return; if (options.fetchOnMount !== false) doFetch(); return () => controllerRef.current?.abort(); }, [keyStr]); if (options.watch) { useEffect(() => { if (isClient) doFetch(); }, options.watch); } const refetch = () => { if (isClient) doFetch(); }; const setData = (updater) => { setState((prev) => { if (!(prev instanceof AsyncData)) return prev; const current = prev.value.isSome() ? prev.value.unwrap() : null; const updated = updater(current); const newData = new AsyncData(Some(updated)); if (options.cache !== false) cache.set(keyStr, newData); return newData; }); }; return [state, refetch, setData]; }; } export function useValync(key, options = {}) { const keyStr = normalizeKey(key); const controllerRef = useRef(null); const [state, setState] = useState(() => { if (options.initialData) { return options.initialData.status === "success" ? new AsyncData(Some(options.initialData.data)) : new AsyncError(options.initialData.error); } if (options.cache !== false && cache.has(keyStr)) { return cache.get(keyStr); } return new AsyncData(None); }); const isClient = typeof window !== "undefined" && typeof AbortController !== "undefined"; const doFetch = () => { controllerRef.current?.abort(); const ctrl = new AbortController(); controllerRef.current = ctrl; if (options.cache !== false && cache.has(keyStr)) { setState(cache.get(keyStr)); return; } setState(new AsyncLoading()); const attempt = (tries) => { fetch(typeof key === "string" ? key : keyStr, { ...options.init, signal: ctrl.signal, }) .then(async (resp) => { let json; try { json = await resp.json(); } catch { return { status: "failed", error: { name: "ParseError", message: "Invalid JSON", }, }; } if (!resp.ok || json.status === "failed") { return { status: "failed", error: json.error ?? { name: "HttpError", message: resp.statusText, code: resp.status, }, }; } return json; }) .then((res) => { if (ctrl.signal.aborted) return; if (res.status === "failed") setState(new AsyncError(res.error)); else { const data = options.onData ? options.onData(res.data) : res.data; const sd = new AsyncData(Some(data)); if (options.cache !== false) cache.set(keyStr, sd); setState(sd); } }) .catch((err) => { if (ctrl.signal.aborted) return; if (tries > 0) return attempt(tries - 1); setState(new AsyncError({ name: "NetworkError", message: err.message, })); }); }; attempt(options.retryCount ?? 0); }; useEffect(() => { if (!isClient || options.initialData) return; if (options.fetchOnMount !== false) doFetch(); return () => controllerRef.current?.abort(); }, [keyStr]); if (options.watch) { useEffect(() => { if (isClient) doFetch(); }, options.watch); } const refetch = () => { if (isClient) doFetch(); }; const setData = (updater) => { setState((prev) => { if (!(prev instanceof AsyncData)) return prev; const current = prev.value.isSome() ? prev.value.unwrap() : null; const updated = updater(current); const newData = new AsyncData(Some(updated)); if (options.cache !== false) cache.set(keyStr, newData); return newData; }); }; return [state, refetch, setData]; } //# sourceMappingURL=index.js.map