@zedux/stores
Version:
The legacy composable store model of Zedux
112 lines (111 loc) • 4.8 kB
JavaScript
;
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;