UNPKG

@zedux/react

Version:

A Molecular State Engine for React

132 lines (131 loc) 6.22 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.useAtomSelector = void 0; const atoms_1 = require("@zedux/atoms"); const react_1 = require("react"); const utils_1 = require("../utils"); const useEcosystem_1 = require("./useEcosystem"); const useReactComponentId_1 = require("./useReactComponentId"); const OPERATION = 'useAtomSelector'; /** * Get the result of running an AtomSelector in the current ecosystem. * * If the exact selector function (or object if it's an AtomSelectorConfig * object) reference + params combo has been used in this ecosystem before, * return the cached result. * * Register a dynamic graph dependency between this React component (as a new * external node) and the AtomSelector. */ const useAtomSelector = (selectorOrConfig, ...args) => { var _a; const ecosystem = (0, useEcosystem_1.useEcosystem)(); const { _graph, selectors } = ecosystem; const dependentKey = (0, useReactComponentId_1.useReactComponentId)(); const [, render] = (0, react_1.useState)(); const existingCache = render.cache; const argsChanged = !existingCache || (selectorOrConfig.argsComparator ? !selectorOrConfig.argsComparator(args, existingCache.args || []) : (0, atoms_1.haveDepsChanged)(existingCache.args, args)); const resolvedArgs = argsChanged ? args : existingCache.args; // if the refs/args don't match, existingCache has refCount: 1, there is no // cache yet for the new ref, and the new ref has the same name, assume it's // an inline selector const isSwappingRefs = existingCache && existingCache.selectorRef !== selectorOrConfig && !argsChanged ? ((_a = _graph.nodes[existingCache.id]) === null || _a === void 0 ? void 0 : _a.refCount) === 1 && !selectors._refBaseKeys.has(selectorOrConfig) && selectors._getIdealCacheId(existingCache.selectorRef) === selectors._getIdealCacheId(selectorOrConfig) : false; if (isSwappingRefs) { // switch `mounted` to false temporarily to prevent circular rerenders ; render.mounted = false; selectors._swapRefs(existingCache, selectorOrConfig, resolvedArgs); render.mounted = true; } let cache = isSwappingRefs ? existingCache : selectors.getCache(selectorOrConfig, resolvedArgs); let edge; const addEdge = (isMaterialized) => { var _a; if (!((_a = _graph.nodes[cache.id]) === null || _a === void 0 ? void 0 : _a.dependents.get(dependentKey))) { edge = _graph.addEdge(dependentKey, cache.id, OPERATION, utils_1.External, () => { if (render.mounted) render({}); }); if (edge) { edge.isMaterialized = isMaterialized; edge.dependentKey = dependentKey; if (cache._lastEdge) { edge.prevEdge = cache._lastEdge; } cache._lastEdge = new WeakRef(edge); } if (selectors._lastCache) { cache._prevCache = selectors._lastCache; } selectors._lastCache = new WeakRef(cache); } }; // Yes, subscribe during render. This operation is idempotent. addEdge(); const renderedResult = cache.result; render.cache = cache; (0, react_1.useEffect)(() => { var _a, _b, _c, _d; cache = isSwappingRefs ? existingCache : selectors.getCache(selectorOrConfig, resolvedArgs); if (edge) { let prevEdge = (_a = edge.prevEdge) === null || _a === void 0 ? void 0 : _a.deref(); edge.isMaterialized = true; // clear out any junk edges added by StrictMode while (prevEdge && !prevEdge.isMaterialized) { ecosystem._graph.removeEdge(prevEdge.dependentKey, cache.id); // mark in case of circular references (shouldn't happen, but just for // consistency with the prevCache algorithm) prevEdge.isMaterialized = true; prevEdge = (_b = prevEdge.prevEdge) === null || _b === void 0 ? void 0 : _b.deref(); } } let prevCache = (_c = cache._prevCache) === null || _c === void 0 ? void 0 : _c.deref(); cache.isMaterialized = true; // clear out any junk caches created by StrictMode while (prevCache && !prevCache.isMaterialized) { selectors.destroyCache(prevCache, [], true); // mark in case of circular references (can happen in certain React // rendering sequences) prevCache.isMaterialized = true; prevCache = (_d = prevCache._prevCache) === null || _d === void 0 ? void 0 : _d.deref(); } // Try adding the edge again (will be a no-op unless React's StrictMode ran // this effect's cleanup unnecessarily OR other effects in child components // cleaned up this component's edges before it could materialize them. // That's fine, just recreate them with `isMaterialized: true` now) addEdge(true); render.mounted = true; // an unmounting component's effect cleanup can force-destroy the selector // or update the state of its dependencies (causing it to rerun) before we // set `render.mounted`. If that happened, trigger a rerender to recreate // the selector and/or get its new state if (cache.isDestroyed || cache.result !== renderedResult) { render({}); } return () => { // remove the edge immediately - no need for a delay here. When StrictMode // double-invokes (invokes, then cleans up, then re-invokes) this effect, // it's expected that selectors and `ttl: 0` atoms with no other // dependents get destroyed and recreated - that's part of what StrictMode // is ensuring _graph.removeEdge(dependentKey, cache.id); // no need to set `render.mounted` to false here }; }, [cache.id]); return renderedResult; }; exports.useAtomSelector = useAtomSelector;