use-between
Version:
How to share React hooks state between components
337 lines (328 loc) • 9.75 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var React = require('react');
function _interopNamespace(e) {
if (e && e.__esModule) return 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__*/_interopNamespace(React);
const ReactSharedInternals = React__namespace.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
const ReactCurrentDispatcher = ReactSharedInternals.ReactCurrentDispatcher;
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();
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;