react-singleton-hook
Version:
Share custom hook state across all components
233 lines (214 loc) • 8.37 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('react-dom')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', 'react-dom'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.ReactSingletonHook = {}, global.React, global.ReactDOM));
})(this, (function (exports, React, require$$0) { 'use strict';
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
var React__default = /*#__PURE__*/_interopDefaultLegacy(React);
var require$$0__default = /*#__PURE__*/_interopDefaultLegacy(require$$0);
function _extends() {
_extends = Object.assign ? Object.assign.bind() : function (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
target[key] = source[key];
}
}
}
return target;
};
return _extends.apply(this, arguments);
}
var SingleItemContainer = function SingleItemContainer(_ref) {
var initValue = _ref.initValue,
useHookBody = _ref.useHookBody,
applyStateChange = _ref.applyStateChange;
var lastState = React.useRef(initValue);
if (typeof useHookBody !== 'function') {
throw new Error("function expected as hook body parameter. got " + typeof useHookBody);
}
var val = useHookBody();
//useLayoutEffect is safe from SSR perspective because SingleItemContainer should never be rendered on server
React.useLayoutEffect(function () {
if (lastState.current !== val) {
lastState.current = val;
applyStateChange(val);
}
}, [applyStateChange, val]);
return null;
};
var createRoot;
var m = require$$0__default["default"];
{
var i = m.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED;
createRoot = function(c, o) {
i.usingClientEntryPoint = true;
try {
return m.createRoot(c, o);
} finally {
i.usingClientEntryPoint = false;
}
};
}
var warning = function warning(message) {
if (console && console.warn) {
console.warn(message);
}
};
// from https://github.com/purposeindustries/window-or-global/blob/master/lib/index.js
// avoid direct usage of 'window' because `window is not defined` error might happen in babel-node
var globalObject = typeof self === 'object' && self.self === self && self || typeof global === 'object' && global.global === global && global || this;
var batch = function batch(cb) {
return require$$0.unstable_batchedUpdates(cb);
};
var mount = function mount(C) {
if (globalObject.document && globalObject.document.createElement) {
var container = globalObject.document.createElement('div');
var root = createRoot(container);
root.render( /*#__PURE__*/React__default["default"].createElement(C, {
automaticContainerInternalUseOnly: true
}));
} else {
warning('Can not mount SingletonHooksContainer on server side. ' + 'Did you manage to run useEffect on server? ' + 'Please mount SingletonHooksContainer into your components tree manually.');
}
};
var nextKey = 1;
var automaticRender = false;
var manualRender = false;
var workingSet = [];
var renderedContainers = [];
var notifyContainersAsync = function notifyContainersAsync() {
renderedContainers.forEach(function (updateRenderedHooks) {
return updateRenderedHooks();
});
};
var SingletonHooksContainer = function SingletonHooksContainer(_ref) {
var automaticContainerInternalUseOnly = _ref.automaticContainerInternalUseOnly;
var _useState = React.useState([]),
hooks = _useState[0],
setHooks = _useState[1];
var currentHooksRef = React.useRef();
currentHooksRef.current = hooks;
// if there was no automaticRender, and this one is not automatic as well
if (!automaticContainerInternalUseOnly && automaticRender === false) {
manualRender = true;
}
React.useEffect(function () {
var mounted = true;
function updateRenderedHooks() {
if (!mounted) return;
if (renderedContainers[0] !== updateRenderedHooks) {
if (!automaticContainerInternalUseOnly && automaticRender === true) {
warning('SingletonHooksContainer is mounted after some singleton hook has been used.' + 'Your SingletonHooksContainer will not be used in favor of internal one.');
}
setHooks(function (_) {
return [];
});
return;
}
setHooks([].concat(workingSet));
}
renderedContainers.push(updateRenderedHooks);
notifyContainersAsync();
return function () {
mounted = false;
if (currentHooksRef.current.length > 0) {
warning('SingletonHooksContainer is unmounted, but it has active singleton hooks. ' + 'They will be reevaluated once SingletonHooksContainer is mounted again');
}
renderedContainers.splice(renderedContainers.indexOf(updateRenderedHooks), 1);
notifyContainersAsync();
};
}, [automaticContainerInternalUseOnly]);
return /*#__PURE__*/React__default["default"].createElement(React__default["default"].Fragment, null, hooks.map(function (_ref2) {
var hook = _ref2.hook,
key = _ref2.key;
return /*#__PURE__*/React__default["default"].createElement(SingleItemContainer, _extends({}, hook, {
key: key
}));
}));
};
var addHook = function addHook(hook) {
var key = nextKey++;
workingSet.push({
hook: hook,
key: key
});
// no container and no previous manually rendered containers
if (renderedContainers.length === 0 && manualRender === false) {
automaticRender = true;
mount(SingletonHooksContainer);
}
notifyContainersAsync();
return function () {
workingSet.splice(workingSet.findIndex(function (h) {
return h.key === key;
}), 1);
notifyContainersAsync();
};
};
var singletonHook = function singletonHook(initValue, useHookBody, options) {
if (options === void 0) {
options = {};
}
var mounted = false;
var removeHook = undefined;
var initStateCalculated = false;
var lastKnownState = undefined;
var consumers = [];
var _options = options,
_options$unmountIfNoC = _options.unmountIfNoConsumers,
unmountIfNoConsumers = _options$unmountIfNoC === void 0 ? false : _options$unmountIfNoC;
var applyStateChange = function applyStateChange(newState) {
lastKnownState = newState;
batch(function () {
return consumers.forEach(function (c) {
return c(newState);
});
});
};
var stateInitializer = function stateInitializer() {
if (!initStateCalculated) {
lastKnownState = typeof initValue === 'function' ? initValue() : initValue;
initStateCalculated = true;
}
return lastKnownState;
};
return function () {
var _useState = React.useState(stateInitializer),
state = _useState[0],
setState = _useState[1];
React.useEffect(function () {
if (!mounted) {
mounted = true;
removeHook = addHook({
initValue: initValue,
useHookBody: useHookBody,
applyStateChange: applyStateChange
});
}
consumers.push(setState);
if (lastKnownState !== state) {
setState(lastKnownState);
}
return function () {
consumers.splice(consumers.indexOf(setState), 1);
if (consumers.length === 0 && unmountIfNoConsumers) {
removeHook();
mounted = false;
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return state;
};
};
var ReactSingletonHook = {
singletonHook: singletonHook,
SingletonHooksContainer: SingletonHooksContainer
};
exports.SingletonHooksContainer = SingletonHooksContainer;
exports["default"] = ReactSingletonHook;
exports.singletonHook = singletonHook;
Object.defineProperty(exports, '__esModule', { value: true });
}));