UNPKG

mobx-react-lite

Version:

Lightweight React bindings for MobX based on function components and Hooks

265 lines (250 loc) 10.3 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var mobx = require('mobx'); var React = require('react'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } var React__default = /*#__PURE__*/_interopDefaultLegacy(React); var _getGlobalState2; if (!React.useState || !React.useSyncExternalStore) { throw new Error("mobx-react-lite requires React 18 or later"); } if (!((mobx._getGlobalState == null || (_getGlobalState2 = mobx._getGlobalState()) == null ? void 0 : _getGlobalState2.version) >= 7)) { throw new Error("mobx-react-lite requires mobx at least version 7 to be available"); } const REGISTRY_FINALIZE_AFTER = 10000; const REGISTRY_SWEEP_INTERVAL = 10000; class TimerBasedFinalizationRegistry { constructor(finalize) { this.finalize = void 0; this.registrations = new Map(); this.sweepTimeout = void 0; // Bound so it can be used directly as setTimeout callback. this.sweep = (maxAge = REGISTRY_FINALIZE_AFTER) => { // cancel timeout so we can force sweep anytime clearTimeout(this.sweepTimeout); this.sweepTimeout = undefined; const now = Date.now(); this.registrations.forEach((registration, token) => { if (now - registration.registeredAt >= maxAge) { this.finalize(registration.value); this.registrations.delete(token); } }); if (this.registrations.size > 0) { this.scheduleSweep(); } }; // Bound so it can be exported directly as clearTimers test utility. this.finalizeAllImmediately = () => { this.sweep(0); }; this.finalize = finalize; } // Token is actually required with this impl register(target, value, token) { this.registrations.set(token, { value, registeredAt: Date.now() }); this.scheduleSweep(); } unregister(token) { this.registrations.delete(token); } scheduleSweep() { if (this.sweepTimeout === undefined) { this.sweepTimeout = setTimeout(this.sweep, REGISTRY_SWEEP_INTERVAL); } } } const UniversalFinalizationRegistry = typeof FinalizationRegistry !== "undefined" ? FinalizationRegistry : TimerBasedFinalizationRegistry; const observerFinalizationRegistry = /*#__PURE__*/new UniversalFinalizationRegistry(adm => { var _adm$reaction; (_adm$reaction = adm.reaction) == null || _adm$reaction.dispose(); adm.reaction = null; }); let globalIsUsingStaticRendering = false; function enableStaticRendering(enable) { globalIsUsingStaticRendering = enable; } function isUsingStaticRendering() { return globalIsUsingStaticRendering; } function createReaction(adm) { adm.reaction = new mobx.Reaction(`observer${adm.name}`, () => { adm.stateVersion = Symbol(); // onStoreChange won't be available until the component "mounts". // If state changes in between initial render and mount, // `useSyncExternalStore` should handle that by checking the state version and issuing update. adm.onStoreChange == null || adm.onStoreChange(); }); } function useObserver(render, baseComponentName = "observed") { if (isUsingStaticRendering()) { return render(); } const admRef = React__default["default"].useRef(null); if (!admRef.current) { // First render const _adm = { reaction: null, onStoreChange: null, stateVersion: Symbol(), name: baseComponentName, subscribe(onStoreChange) { // Do NOT access admRef here! observerFinalizationRegistry.unregister(_adm); _adm.onStoreChange = onStoreChange; if (!_adm.reaction) { // We've lost our reaction and therefore all subscriptions, occurs when: // 1. Timer based finalization registry disposed reaction before component mounted. // 2. React "re-mounts" same component without calling render in between (typically <StrictMode>). // We have to recreate reaction and schedule re-render to recreate subscriptions, // even if state did not change. createReaction(_adm); // `onStoreChange` won't force update if subsequent `getSnapshot` returns same value. // So we make sure that is not the case _adm.stateVersion = Symbol(); } return () => { var _adm$reaction; // Do NOT access admRef here! _adm.onStoreChange = null; (_adm$reaction = _adm.reaction) == null || _adm$reaction.dispose(); _adm.reaction = null; }; }, getSnapshot() { // Do NOT access admRef here! return _adm.stateVersion; } }; admRef.current = _adm; } const adm = admRef.current; if (!adm.reaction) { // First render or reaction was disposed by registry before subscribe createReaction(adm); // StrictMode/ConcurrentMode/Suspense may mean that our component is // rendered and abandoned multiple times, so we need to track leaked // Reactions. observerFinalizationRegistry.register(admRef, adm, adm); } React__default["default"].useDebugValue(adm.reaction, mobx.getDependencyTree); React__default["default"].useSyncExternalStore( // Both of these must be stable, otherwise it would keep resubscribing every render. adm.subscribe, adm.getSnapshot, adm.getSnapshot); // render the original component, but have the // reaction track the observables, so that rendering // can be invalidated (see above) once a dependency changes let renderResult; let exception; adm.reaction.track(() => { try { renderResult = render(); } catch (e) { exception = e; } }); if (exception) { throw exception; // re-throw any exceptions caught during rendering } return renderResult; } var _Object$getOwnPropert, _Object$getOwnPropert2; const hasSymbol = typeof Symbol === "function" && Symbol.for; const isFunctionNameConfigurable = (_Object$getOwnPropert = (_Object$getOwnPropert2 = /*#__PURE__*/Object.getOwnPropertyDescriptor(() => {}, "name")) == null ? void 0 : _Object$getOwnPropert2.configurable) != null ? _Object$getOwnPropert : false; // Using react-is had some issues (and operates on elements, not on types), see #608 / #609 const ReactForwardRefSymbol = hasSymbol ? /*#__PURE__*/Symbol.for("react.forward_ref") : typeof React.forwardRef === "function" && React.forwardRef(props => null)["$$typeof"]; const ReactMemoSymbol = hasSymbol ? /*#__PURE__*/Symbol.for("react.memo") : typeof React.memo === "function" && React.memo(props => null)["$$typeof"]; // n.b. base case is not used for actual typings or exported in the typing files function observer(baseComponent) { if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) { throw new Error(`[mobx-react-lite] You are trying to use \`observer\` on a function component wrapped in either another \`observer\` or \`React.memo\`. The observer already applies 'React.memo' for you.`); } // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 if (isUsingStaticRendering()) { return baseComponent; } let useForwardRef = false; let render = baseComponent; const baseComponentName = baseComponent.displayName || baseComponent.name; // If already wrapped with forwardRef, unwrap, // so we can patch render and apply memo if (ReactForwardRefSymbol && baseComponent["$$typeof"] === ReactForwardRefSymbol) { useForwardRef = true; render = baseComponent["render"]; if (typeof render !== "function") { throw new Error(`[mobx-react-lite] \`render\` property of ForwardRef was not a function`); } } let observerComponent = (props, ref) => { return useObserver(() => render(props, ref), baseComponentName); }; observerComponent.displayName = baseComponent.displayName; if (isFunctionNameConfigurable) { Object.defineProperty(observerComponent, "name", { value: baseComponent.name, writable: true, configurable: true }); } if (useForwardRef) { // `forwardRef` must be applied prior `memo` // `forwardRef(observer(cmp))` throws: // "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))" observerComponent = React.forwardRef(observerComponent); } // memo; we are not interested in deep updates // in props; we assume that if deep objects are changed, // this is in observables, which would have been tracked anyway observerComponent = React.memo(observerComponent); copyStaticProperties(baseComponent, observerComponent); return observerComponent; } // based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js const hoistBlackList = { $$typeof: true, render: true, compare: true, type: true, // Don't redefine `displayName`, // it's defined as getter-setter pair on `memo` (see #3192). displayName: true }; function copyStaticProperties(base, target) { Object.keys(base).forEach(key => { if (!hoistBlackList[key]) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key)); } }); } function ObserverComponent({ children, render }) { if (children && render) { console.error("MobX Observer: Do not use children and render in the same time in `Observer`"); } const component = children || render; if (typeof component !== "function") { return null; } return useObserver(component); } ObserverComponent.displayName = "Observer"; function useLocalObservable(initializer, annotations) { return React.useState(() => mobx.observable(initializer(), annotations, { autoBind: true }))[0]; } var _observerFinalization; const clearTimers = (_observerFinalization = observerFinalizationRegistry["finalizeAllImmediately"]) != null ? _observerFinalization : () => {}; exports.Observer = ObserverComponent; exports._observerFinalizationRegistry = observerFinalizationRegistry; exports.clearTimers = clearTimers; exports.enableStaticRendering = enableStaticRendering; exports.isUsingStaticRendering = isUsingStaticRendering; exports.observer = observer; exports.useLocalObservable = useLocalObservable; //# sourceMappingURL=mobxreactlite.cjs.development.js.map