@risingstack/react-easy-state
Version:
React state management with a minimal API. Made with ES6 Proxies.
611 lines (496 loc) • 18.9 kB
JavaScript
;
Object.defineProperty(exports, '__esModule', { value: true });
var react = require('react');
var observerUtil = require('@nx-js/observer-util');
var reactPlatform_cjs = require('./react-platform.cjs');
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError("Cannot call a class as a function");
}
}
function _defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ("value" in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
function _createClass(Constructor, protoProps, staticProps) {
if (protoProps) _defineProperties(Constructor.prototype, protoProps);
if (staticProps) _defineProperties(Constructor, staticProps);
return Constructor;
}
function _defineProperty(obj, key, value) {
if (key in obj) {
Object.defineProperty(obj, key, {
value: value,
enumerable: true,
configurable: true,
writable: true
});
} else {
obj[key] = value;
}
return obj;
}
function ownKeys(object, enumerableOnly) {
var keys = Object.keys(object);
if (Object.getOwnPropertySymbols) {
var symbols = Object.getOwnPropertySymbols(object);
if (enumerableOnly) symbols = symbols.filter(function (sym) {
return Object.getOwnPropertyDescriptor(object, sym).enumerable;
});
keys.push.apply(keys, symbols);
}
return keys;
}
function _objectSpread2(target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i] != null ? arguments[i] : {};
if (i % 2) {
ownKeys(Object(source), true).forEach(function (key) {
_defineProperty(target, key, source[key]);
});
} else if (Object.getOwnPropertyDescriptors) {
Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
} else {
ownKeys(Object(source)).forEach(function (key) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
});
}
}
return target;
}
function _inherits(subClass, superClass) {
if (typeof superClass !== "function" && superClass !== null) {
throw new TypeError("Super expression must either be null or a function");
}
subClass.prototype = Object.create(superClass && superClass.prototype, {
constructor: {
value: subClass,
writable: true,
configurable: true
}
});
if (superClass) _setPrototypeOf(subClass, superClass);
}
function _getPrototypeOf(o) {
_getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
return o.__proto__ || Object.getPrototypeOf(o);
};
return _getPrototypeOf(o);
}
function _setPrototypeOf(o, p) {
_setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
o.__proto__ = p;
return o;
};
return _setPrototypeOf(o, p);
}
function _assertThisInitialized(self) {
if (self === void 0) {
throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
}
return self;
}
function _possibleConstructorReturn(self, call) {
if (call && (typeof call === "object" || typeof call === "function")) {
return call;
}
return _assertThisInitialized(self);
}
function _superPropBase(object, property) {
while (!Object.prototype.hasOwnProperty.call(object, property)) {
object = _getPrototypeOf(object);
if (object === null) break;
}
return object;
}
function _get(target, property, receiver) {
if (typeof Reflect !== "undefined" && Reflect.get) {
_get = Reflect.get;
} else {
_get = function _get(target, property, receiver) {
var base = _superPropBase(target, property);
if (!base) return;
var desc = Object.getOwnPropertyDescriptor(base, property);
if (desc.get) {
return desc.get.call(receiver);
}
return desc.value;
};
}
return _get(target, property, receiver || target);
}
function _slicedToArray(arr, i) {
return _arrayWithHoles(arr) || _iterableToArrayLimit(arr, i) || _nonIterableRest();
}
function _toConsumableArray(arr) {
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread();
}
function _arrayWithoutHoles(arr) {
if (Array.isArray(arr)) {
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i];
return arr2;
}
}
function _arrayWithHoles(arr) {
if (Array.isArray(arr)) return arr;
}
function _iterableToArray(iter) {
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter);
}
function _iterableToArrayLimit(arr, i) {
if (!(Symbol.iterator in Object(arr) || Object.prototype.toString.call(arr) === "[object Arguments]")) {
return;
}
var _arr = [];
var _n = true;
var _d = false;
var _e = undefined;
try {
for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) {
_arr.push(_s.value);
if (i && _arr.length === i) break;
}
} catch (err) {
_d = true;
_e = err;
} finally {
try {
if (!_n && _i["return"] != null) _i["return"]();
} finally {
if (_d) throw _e;
}
}
return _arr;
}
function _nonIterableSpread() {
throw new TypeError("Invalid attempt to spread non-iterable instance");
}
function _nonIterableRest() {
throw new TypeError("Invalid attempt to destructure non-iterable instance");
}
// it is window in the DOM and global in NodeJS and React Native
var isDOM = typeof window !== 'undefined';
var isNative = typeof global !== 'undefined';
var globalObj = isDOM ? window : isNative ? global : undefined;
var hasHooks = typeof react.useState === 'function';
var isInsideFunctionComponent = false;
var isInsideClassComponentRender = false;
var isInsideFunctionComponentWithoutHooks = false;
var COMPONENT = Symbol('owner component');
function mapStateToStores(state) {
// find store properties and map them to their none observable raw value
// to do not trigger none static this.setState calls
// from the static getDerivedStateFromProps lifecycle method
var component = state[COMPONENT];
return Object.keys(component).map(function (key) {
return component[key];
}).filter(observerUtil.isObservable).map(observerUtil.raw);
}
function view(Comp) {
var isStatelessComp = !(Comp.prototype && Comp.prototype.isReactComponent);
var ReactiveComp;
if (isStatelessComp && hasHooks) {
// use a hook based reactive wrapper when we can
ReactiveComp = function ReactiveComp(props) {
// use a dummy setState to update the component
var _useState = react.useState(),
_useState2 = _slicedToArray(_useState, 2),
setState = _useState2[1]; // create a memoized reactive wrapper of the original component (render)
// at the very first run of the component function
var render = react.useMemo(function () {
return observerUtil.observe(Comp, {
scheduler: function scheduler() {
return setState({});
},
lazy: true
});
}, // Adding the original Comp here is necessary to make React Hot Reload work
// it does not affect behavior otherwise
[Comp]); // cleanup the reactive connections after the very last render of the component
react.useEffect(function () {
return function () {
return observerUtil.unobserve(render);
};
}, []); // the isInsideFunctionComponent flag is used to toggle `store` behavior
// based on where it was called from
isInsideFunctionComponent = true;
try {
// run the reactive render instead of the original one
return render(props);
} finally {
isInsideFunctionComponent = false;
}
};
} else {
var BaseComp = isStatelessComp ? react.Component : Comp; // a HOC which overwrites render, shouldComponentUpdate and componentWillUnmount
// it decides when to run the new reactive methods and when to proxy to the original methods
var ReactiveClassComp = /*#__PURE__*/function (_BaseComp) {
_inherits(ReactiveClassComp, _BaseComp);
function ReactiveClassComp(props, context) {
var _this;
_classCallCheck(this, ReactiveClassComp);
_this = _possibleConstructorReturn(this, _getPrototypeOf(ReactiveClassComp).call(this, props, context));
_this.state = _this.state || {};
_this.state[COMPONENT] = _assertThisInitialized(_this); // create a reactive render for the component
_this.render = observerUtil.observe(_this.render, {
scheduler: function scheduler() {
return _this.setState({});
},
lazy: true
});
return _this;
}
_createClass(ReactiveClassComp, [{
key: "render",
value: function render() {
isInsideClassComponentRender = !isStatelessComp;
isInsideFunctionComponentWithoutHooks = isStatelessComp;
try {
return isStatelessComp ? Comp(this.props, this.context) : _get(_getPrototypeOf(ReactiveClassComp.prototype), "render", this).call(this);
} finally {
isInsideClassComponentRender = false;
isInsideFunctionComponentWithoutHooks = false;
}
} // react should trigger updates on prop changes, while easyState handles store changes
}, {
key: "shouldComponentUpdate",
value: function shouldComponentUpdate(nextProps, nextState) {
var props = this.props,
state = this.state; // respect the case when the user defines a shouldComponentUpdate
if (_get(_getPrototypeOf(ReactiveClassComp.prototype), "shouldComponentUpdate", this)) {
return _get(_getPrototypeOf(ReactiveClassComp.prototype), "shouldComponentUpdate", this).call(this, nextProps, nextState);
} // return true if it is a reactive render or state changes
if (state !== nextState) {
return true;
} // the component should update if any of its props shallowly changed value
var keys = Object.keys(props);
var nextKeys = Object.keys(nextProps);
return nextKeys.length !== keys.length || nextKeys.some(function (key) {
return props[key] !== nextProps[key];
});
} // add a custom deriveStoresFromProps lifecyle method
}, {
key: "componentWillUnmount",
value: function componentWillUnmount() {
// call user defined componentWillUnmount
if (_get(_getPrototypeOf(ReactiveClassComp.prototype), "componentWillUnmount", this)) {
_get(_getPrototypeOf(ReactiveClassComp.prototype), "componentWillUnmount", this).call(this);
} // clean up memory used by Easy State
observerUtil.unobserve(this.render);
}
}], [{
key: "getDerivedStateFromProps",
value: function getDerivedStateFromProps(props, state) {
if (_get(_getPrototypeOf(ReactiveClassComp), "deriveStoresFromProps", this)) {
var _get2;
// inject all local stores and let the user mutate them directly
var stores = mapStateToStores(state);
(_get2 = _get(_getPrototypeOf(ReactiveClassComp), "deriveStoresFromProps", this)).call.apply(_get2, [this, props].concat(_toConsumableArray(stores)));
} // respect user defined getDerivedStateFromProps
if (_get(_getPrototypeOf(ReactiveClassComp), "getDerivedStateFromProps", this)) {
return _get(_getPrototypeOf(ReactiveClassComp), "getDerivedStateFromProps", this).call(this, props, state);
}
return null;
}
}]);
return ReactiveClassComp;
}(BaseComp);
ReactiveComp = ReactiveClassComp;
}
ReactiveComp.displayName = Comp.displayName || Comp.name; // static props are inherited by class components,
// but have to be copied for function components
if (isStatelessComp) {
Object.keys(Comp).forEach(function (key) {
ReactiveComp[key] = Comp[key];
});
}
return isStatelessComp && hasHooks ? react.memo(ReactiveComp) : ReactiveComp;
}
var taskQueue = new Set();
var scheduler = {
isOn: false,
add: function add(task) {
if (scheduler.isOn) {
taskQueue.add(task);
} else {
task();
}
},
flush: function flush() {
taskQueue.forEach(function (task) {
return task();
});
taskQueue.clear();
},
on: function on() {
scheduler.isOn = true;
},
off: function off() {
scheduler.isOn = false;
}
};
// until the function is finished running
// react renders are batched by unstable_batchedUpdates
// autoEffects and other custom reactions are batched by our scheduler
function batch(fn, ctx, args) {
// do not apply scheduler logic if it is already applied from a parent function
// it would flush in the middle of the parent's batch
if (scheduler.isOn) {
return reactPlatform_cjs.unstable_batchedUpdates(function () {
return fn.apply(ctx, args);
});
}
try {
scheduler.on();
return reactPlatform_cjs.unstable_batchedUpdates(function () {
return fn.apply(ctx, args);
});
} finally {
scheduler.flush();
scheduler.off();
}
} // this creates and returns a batched version of the passed function
// the cache is necessary to always map the same thing to the same function
// which makes sure that addEventListener/removeEventListener pairs don't break
var cache = new WeakMap();
function batchFn(fn) {
if (typeof fn !== 'function') {
return fn;
}
var batched = cache.get(fn);
if (!batched) {
batched = new Proxy(fn, {
apply: function apply(target, thisArg, args) {
return batch(target, thisArg, args);
}
});
cache.set(fn, batched);
}
return batched;
}
function batchMethodCallbacks(obj, method) {
var descriptor = Object.getOwnPropertyDescriptor(obj, method);
if (descriptor && descriptor.writable && typeof descriptor.value === 'function') {
obj[method] = new Proxy(descriptor.value, {
apply: function apply(target, ctx, args) {
return Reflect.apply(target, ctx, args.map(batchFn));
}
});
}
} // batched obj.addEventListener(cb) like callbacks
function batchMethodsCallbacks(obj, methods) {
methods.forEach(function (method) {
return batchMethodCallbacks(obj, method);
});
}
function batchMethod(obj, method) {
var descriptor = Object.getOwnPropertyDescriptor(obj, method);
if (!descriptor) {
return;
}
var value = descriptor.value,
writable = descriptor.writable,
set = descriptor.set,
configurable = descriptor.configurable;
if (configurable && typeof set === 'function') {
Object.defineProperty(obj, method, _objectSpread2({}, descriptor, {
set: batchFn(set)
}));
} else if (writable && typeof value === 'function') {
obj[method] = batchFn(value);
}
} // batches obj.onevent = fn like calls and store methods
function batchMethods(obj, methods) {
methods = methods || Object.getOwnPropertyNames(obj);
methods.forEach(function (method) {
return batchMethod(obj, method);
});
return obj;
} // do a sync batching for the most common task sources
// this should be removed when React's own batching is improved in the future
// batch timer functions
batchMethodsCallbacks(globalObj, ['setTimeout', 'setInterval', 'requestAnimationFrame', 'requestIdleCallback']);
if (globalObj.Promise) {
batchMethodsCallbacks(Promise.prototype, ['then', 'catch']);
} // Event listener batching causes an input caret jumping bug:
// https://github.com/RisingStack/react-easy-state/issues/92.
// This part has to be commented out to prevent that bug.
// React batches setStates in its event listeners anyways
// so this commenting this part out is not a huge issue.
// batch addEventListener calls
/* if (globalObj.EventTarget) {
batchMethodsCallbacks(EventTarget.prototype, [
'addEventListener',
'removeEventListener',
]);
} */
// this batches websocket event handlers
if (globalObj.WebSocket) {
batchMethods(WebSocket.prototype, ['onopen', 'onmessage', 'onerror', 'onclose']);
} // HTTP event handlers are usually wrapped by Promises, which is covered above
function createStore(obj) {
return batchMethods(observerUtil.observable(typeof obj === 'function' ? obj() : obj));
}
function store(obj) {
// do not create new versions of the store on every render
// if it is a local store in a function component
// create a memoized store at the first call instead
if (isInsideFunctionComponent) {
// useMemo is not a semantic guarantee
// In the future, React may choose to “forget” some previously memoized values and recalculate them on next render
// see this docs for more explanation: https://reactjs.org/docs/hooks-reference.html#usememo
return react.useMemo(function () {
return createStore(obj);
}, []);
}
if (isInsideFunctionComponentWithoutHooks) {
throw new Error('You cannot use state inside a function component with a pre-hooks version of React. Please update your React version to at least v16.8.0 to use this feature.');
}
if (isInsideClassComponentRender) {
throw new Error('You cannot use state inside a render of a class component. Please create your store outside of the render function.');
}
return createStore(obj);
}
function autoEffect(fn) {
var deps = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
if (isInsideFunctionComponent) {
return react.useEffect(function () {
var observer = observerUtil.observe(fn, {
scheduler: function scheduler$1() {
return scheduler.add(observer);
}
});
return function () {
return observerUtil.unobserve(observer);
};
}, deps);
}
if (isInsideFunctionComponentWithoutHooks) {
throw new Error('You cannot use autoEffect inside a function component with a pre-hooks version of React. Please update your React version to at least v16.8.0 to use this feature.');
}
if (isInsideClassComponentRender) {
throw new Error('You cannot use autoEffect inside a render of a class component. Please use it in the constructor or lifecycle methods instead.');
}
var observer = observerUtil.observe(fn, {
scheduler: function scheduler$1() {
return scheduler.add(observer);
}
});
return observer;
}
Object.defineProperty(exports, 'clearEffect', {
enumerable: true,
get: function () {
return observerUtil.unobserve;
}
});
exports.autoEffect = autoEffect;
exports.batch = batch;
exports.store = store;
exports.view = view;
//# sourceMappingURL=cjs.es5.js.map