UNPKG

@zedux/stores

Version:

The legacy composable store model of Zedux

112 lines (111 loc) 4.8 kB
"use strict"; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.injectPromise = void 0; const atoms_1 = require("@zedux/atoms"); const core_1 = require("@zedux/core"); const atoms_port_1 = require("./atoms-port"); const api_1 = require("./api"); const injectStore_1 = require("./injectStore"); /** * Create a memoized promise reference. Kicks off the promise immediately * (unlike injectEffect which waits a tick). Creates a store to track promise * state. This store's state shape is based off React Query: * * ```ts * { * data?: <promise result type> * error?: Error * isError: boolean * isLoading: boolean * isSuccess: boolean * status: 'error' | 'loading' | 'success' * } * ``` * * Returns an Atom API with `.store` and `.promise` set. * * The 2nd `deps` param is just like `injectMemo` - these deps determine when * the promise's reference should change. * * The 3rd `config` param can take the following options: * * - `dataOnly`: Set this to true to prevent the store from tracking promise * status and make your promise's `data` the entire state. * * - `initialState`: Set the initial state of the store (e.g. a placeholder * value before the promise resolves) * * - store config: Any other config options will be passed directly to * `injectStore`'s config. For example, pass `subscribe: false` to * prevent the store from reevaluating the current atom on update. * * ```ts * const promiseApi = injectPromise(async () => { * const response = await fetch(url) * return await response.json() * }, [url], { * dataOnly: true, * initialState: '', * subscribe: false * }) * ``` */ const injectPromise = (promiseFactory, deps, _a = {}) => { var { dataOnly, initialState, runOnInvalidate } = _a, storeConfig = __rest(_a, ["dataOnly", "initialState", "runOnInvalidate"]); const refs = (0, atoms_1.injectRef)({ counter: 0 }); const store = (0, injectStore_1.injectStore)(dataOnly ? initialState : (0, atoms_port_1.getInitialPromiseState)(initialState), storeConfig); if (runOnInvalidate && // injectWhy is an unrestricted injector - using it conditionally is fine: (0, atoms_1.injectWhy)().some(reason => reason.type === 'cache invalidated')) { refs.current.counter++; } // setting a ref during evaluation is perfectly fine in Zedux refs.current.promise = (0, atoms_1.injectMemo)(() => { const prevController = refs.current.controller; const nextController = typeof AbortController !== 'undefined' ? new AbortController() : undefined; refs.current.controller = nextController; const promise = promiseFactory(refs.current.controller); if (true /* DEV */ && typeof (promise === null || promise === void 0 ? void 0 : promise.then) !== 'function') { throw new TypeError(`Zedux: injectPromise expected callback to return a promise. Received ${(0, core_1.detailedTypeof)(promise)}`); } if (promise === refs.current.promise) return refs.current.promise; if (prevController) prevController.abort('updated'); if (!dataOnly) { // preserve previous data and error using setStateDeep: store.setStateDeep(state => (0, atoms_port_1.getInitialPromiseState)(state.data)); } promise .then(data => { if (nextController === null || nextController === void 0 ? void 0 : nextController.signal.aborted) return; store.setState(dataOnly ? data : (0, atoms_port_1.getSuccessPromiseState)(data)); }) .catch(error => { if (dataOnly || (nextController === null || nextController === void 0 ? void 0 : nextController.signal.aborted)) return; // preserve previous data using setStateDeep: store.setStateDeep((0, atoms_port_1.getErrorPromiseState)(error)); }); return promise; }, deps && [...deps, refs.current.counter]); (0, atoms_1.injectEffect)(() => () => { const controller = refs.current.controller; if (controller) controller.abort('destroyed'); }, []); return (0, api_1.api)(store).setPromise(refs.current.promise); }; exports.injectPromise = injectPromise;