use-between
Version:
How to share React hooks state between components
357 lines (349 loc) • 10.3 kB
JavaScript
;
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;