UNPKG

use-between

Version:

How to share React hooks state between components

357 lines (349 loc) 10.3 kB
'use strict'; var React = require('react'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React); var _a, _b, _c; /* istanbul ignore next */ const ReactSharedInternals = // React 18 and below (_c = (_b = (_a = React__namespace.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) !== null && _a !== void 0 ? _a : // React 19 React__namespace.__CLIENT_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE) !== null && _b !== void 0 ? _b : // React 19 React__namespace.__SERVER_INTERNALS_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE) !== null && _c !== void 0 ? _c : // React 19 React__namespace._DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE; function resolveDispatcher() { if ("ReactCurrentDispatcher" in ReactSharedInternals) { /* istanbul ignore next */ // React 18 and below return ReactSharedInternals.ReactCurrentDispatcher; } else { // React 19 return Object.defineProperty({}, 'current', { get: () => { var _a; return (_a = ReactSharedInternals.H) !== null && _a !== void 0 ? _a : undefined; }, set: (v) => { ReactSharedInternals.H = v; }, }); } } const ReactCurrentDispatcher = resolveDispatcher(); const useForceUpdate = () => React.useReducer(() => ({}))[1]; const notImplemented = (name) => () => { const msg = `Hook "${name}" no possible to using inside useBetween scope.`; console.error(msg); throw new Error(msg); }; const equals = (a, b) => Object.is(a, b); const shouldUpdate = (a, b) => ((!a || !b) || (a.length !== b.length) || a.some((dep, index) => !equals(dep, b[index]))); const detectServer = () => typeof window === 'undefined'; const instances = new Map(); let boxes = []; let pointer = 0; let useEffectQueue = []; let useLayoutEffectQueue = []; let nextTick = () => { }; let isServer = detectServer(); let initialData = undefined; const nextBox = () => { const index = pointer++; return (boxes[index] = boxes[index] || {}); }; const ownDisptacher = { useState(initialState) { const box = nextBox(); const tick = nextTick; if (!box.initialized) { box.state = typeof initialState === "function" ? initialState() : initialState; box.set = (fn) => { if (typeof fn === 'function') { return box.set(fn(box.state)); } if (!equals(fn, box.state)) { box.state = fn; tick(); } }; box.initialized = true; } return [box.state, box.set]; }, useReducer(reducer, initialState, init) { const box = nextBox(); const tick = nextTick; if (!box.initialized) { box.state = init ? init(initialState) : initialState; box.dispatch = (action) => { const state = reducer(box.state, action); if (!equals(state, box.state)) { box.state = state; tick(); } }; box.initialized = true; } return [box.state, box.dispatch]; }, useEffect(fn, deps) { if (isServer) return; const box = nextBox(); if (!box.initialized) { box.deps = deps; box.initialized = true; useEffectQueue.push([box, deps, fn]); } else if (shouldUpdate(box.deps, deps)) { box.deps = deps; useEffectQueue.push([box, deps, fn]); } }, useLayoutEffect(fn, deps) { if (isServer) return; const box = nextBox(); if (!box.initialized) { box.deps = deps; box.initialized = true; useLayoutEffectQueue.push([box, deps, fn]); } else if (shouldUpdate(box.deps, deps)) { box.deps = deps; useLayoutEffectQueue.push([box, deps, fn]); } }, useCallback(fn, deps) { const box = nextBox(); if (!box.initialized) { box.fn = fn; box.deps = deps; box.initialized = true; } else if (shouldUpdate(box.deps, deps)) { box.deps = deps; box.fn = fn; } return box.fn; }, useMemo(fn, deps) { const box = nextBox(); if (!box.initialized) { box.deps = deps; box.state = fn(); box.initialized = true; } else if (shouldUpdate(box.deps, deps)) { box.deps = deps; box.state = fn(); } return box.state; }, useRef(initialValue) { const box = nextBox(); if (!box.initialized) { box.state = { current: initialValue }; box.initialized = true; } return box.state; }, useImperativeHandle(ref, fn, deps) { if (isServer) return; const box = nextBox(); if (!box.initialized) { box.deps = deps; box.initialized = true; useLayoutEffectQueue.push([box, deps, () => { typeof ref === 'function' ? ref(fn()) : ref.current = fn(); }]); } else if (shouldUpdate(box.deps, deps)) { box.deps = deps; useLayoutEffectQueue.push([box, deps, () => { typeof ref === 'function' ? ref(fn()) : ref.current = fn(); }]); } } }; [ 'readContext', 'useContext', 'useDebugValue', 'useResponder', 'useDeferredValue', 'useTransition' ].forEach(key => ownDisptacher[key] = notImplemented(key)); const factory = (hook, options) => { const scopedBoxes = []; let syncs = []; let state = undefined; let unsubs = []; let mocked = false; if (options && options.mock) { state = options.mock; mocked = true; } const sync = () => { syncs.slice().forEach(fn => fn()); }; const tick = () => { if (mocked) return; const originDispatcher = ReactCurrentDispatcher.current; const originState = [ pointer, useEffectQueue, useLayoutEffectQueue, boxes, nextTick ]; let tickAgain = false; let tickBody = true; pointer = 0; useEffectQueue = []; useLayoutEffectQueue = []; boxes = scopedBoxes; nextTick = () => { if (tickBody) { tickAgain = true; } else { tick(); } }; ReactCurrentDispatcher.current = ownDisptacher; state = hook(initialData); [useLayoutEffectQueue, useEffectQueue].forEach(queue => (queue.forEach(([box, deps, fn]) => { box.deps = deps; if (box.unsub) { const unsub = box.unsub; unsubs = unsubs.filter(fn => fn !== unsub); unsub(); } const unsub = fn(); if (typeof unsub === "function") { unsubs.push(unsub); box.unsub = unsub; } else { box.unsub = null; } }))); [ pointer, useEffectQueue, useLayoutEffectQueue, boxes, nextTick ] = originState; ReactCurrentDispatcher.current = originDispatcher; tickBody = false; if (!tickAgain) { sync(); return; } tick(); }; const sub = (fn) => { if (syncs.indexOf(fn) === -1) { syncs.push(fn); } }; const unsub = (fn) => { syncs = syncs.filter(f => f !== fn); }; const mock = (obj) => { mocked = true; state = obj; sync(); }; const unmock = () => { mocked = false; tick(); }; return { init: () => tick(), get: () => state, sub, unsub, unsubs: () => unsubs, mock, unmock }; }; const getInstance = (hook) => { let inst = instances.get(hook); if (!inst) { inst = factory(hook); instances.set(hook, inst); inst.init(); } return inst; }; const useBetween = (hook) => { const forceUpdate = useForceUpdate(); let inst = getInstance(hook); inst.sub(forceUpdate); React.useEffect(() => (inst.sub(forceUpdate), () => inst.unsub(forceUpdate)), [inst, forceUpdate]); return inst.get(); }; const useInitial = (data, server) => { const ref = React.useRef(0); if (!ref.current) { isServer = typeof server === 'undefined' ? detectServer() : server; isServer && clear(); initialData = data; ref.current = 1; } }; const mock = (hook, state) => { let inst = instances.get(hook); if (inst) inst.mock(state); else { inst = factory(hook, { mock: state }); instances.set(hook, inst); } return inst.unmock; }; const get = (hook) => getInstance(hook).get(); const free = function (...hooks) { if (!hooks.length) { hooks = []; instances.forEach((_instance, hook) => hooks.push(hook)); } let inst; hooks.forEach((hook) => ((inst = instances.get(hook)) && inst.unsubs().slice().forEach((fn) => fn()))); hooks.forEach((hook) => instances.delete(hook)); }; const clear = () => instances.clear(); const on = (hook, fn) => { const inst = getInstance(hook); const listener = () => fn(inst.get()); inst.sub(listener); return () => inst.unsub(listener); }; exports.clear = clear; exports.free = free; exports.get = get; exports.mock = mock; exports.on = on; exports.useBetween = useBetween; exports.useInitial = useInitial;