mobx-react-lite
Version:
Lightweight React bindings for MobX based on React 16.8 and Hooks
336 lines (314 loc) • 13.3 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mobx'), require('react')) :
typeof define === 'function' && define.amd ? define(['exports', 'mobx', 'react'], factory) :
(global = global || self, factory(global.mobxReactLite = {}, global.mobx, global.React));
}(this, function (exports, mobx, React) { 'use strict';
var React__default = 'default' in React ? React['default'] : React;
if (!React.useState) {
throw new Error("mobx-react-lite requires React with Hooks support");
}
if (!mobx.spy) {
throw new Error("mobx-react-lite requires mobx at least version 4 to be available");
}
function useObservable(initialValue) {
var observableRef = React.useRef(null);
if (!observableRef.current) {
observableRef.current = mobx.observable(initialValue);
}
return observableRef.current;
}
function useComputed(func, inputs) {
if (inputs === void 0) { inputs = []; }
var computed = React.useMemo(function () { return mobx.computed(func); }, inputs);
return computed.get();
}
var doNothingDisposer = function () {
// empty
};
/**
* Adds an observable effect (reaction, autorun, or anything else that returns a disposer) that will be registered upon component creation and disposed upon unmounting.
* Returns the generated disposer for early disposal.
*
* @export
* @template D
* @param {() => D} disposerGenerator A function that returns the disposer of the wanted effect.
* @param {ReadonlyArray<any>} [inputs=[]] If you want the effect to be automatically re-created when some variable(s) are changed then pass them in this array.
* @returns {D}
*/
function useDisposable(disposerGenerator, inputs) {
if (inputs === void 0) { inputs = []; }
var disposerRef = React.useRef(null);
var earlyDisposedRef = React.useRef(false);
React.useEffect(function () {
return lazyCreateDisposer(false);
}, inputs);
function lazyCreateDisposer(earlyDisposal) {
// ensure that we won't create a new disposer if it was early disposed
if (earlyDisposedRef.current) {
return doNothingDisposer;
}
if (!disposerRef.current) {
var newDisposer = disposerGenerator();
if (typeof newDisposer !== "function") {
var error = new Error("generated disposer must be a function");
{
// tslint:disable-next-line:no-console
console.error(error);
return doNothingDisposer;
}
}
disposerRef.current = newDisposer;
}
return function () {
if (disposerRef.current) {
disposerRef.current();
disposerRef.current = null;
}
if (earlyDisposal) {
earlyDisposedRef.current = true;
}
};
}
return lazyCreateDisposer(true);
}
var globalIsUsingStaticRendering = false;
function useStaticRendering(enable) {
globalIsUsingStaticRendering = enable;
}
function isUsingStaticRendering() {
return globalIsUsingStaticRendering;
}
/*! *****************************************************************************
Copyright (c) Microsoft Corporation. All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use
this file except in compliance with the License. You may obtain a copy of the
License at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED
WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE,
MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions
and limitations under the License.
***************************************************************************** */
var __assign = function() {
__assign = Object.assign || function __assign(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
function __read(o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
}
function printDebugValue(v) {
if (!v.current) {
return "<unknown>";
}
return mobx.getDependencyTree(v.current);
}
var EMPTY_ARRAY = [];
function useUnmount(fn) {
React.useEffect(function () { return fn; }, EMPTY_ARRAY);
}
function useForceUpdate() {
var _a = __read(React.useState(0), 2), setTick = _a[1];
var update = React.useCallback(function () {
setTick(function (tick) { return tick + 1; });
}, []);
return update;
}
function isPlainObject(value) {
if (!value || typeof value !== "object") {
return false;
}
var proto = Object.getPrototypeOf(value);
return !proto || proto === Object.prototype;
}
var EMPTY_OBJECT = {};
function useObserver(fn, baseComponentName, options) {
if (baseComponentName === void 0) { baseComponentName = "observed"; }
if (options === void 0) { options = EMPTY_OBJECT; }
if (isUsingStaticRendering()) {
return fn();
}
var wantedForceUpdateHook = options.useForceUpdate || useForceUpdate;
var forceUpdate = wantedForceUpdateHook();
var reaction = React.useRef(null);
if (!reaction.current) {
reaction.current = new mobx.Reaction("observer(" + baseComponentName + ")", function () {
forceUpdate();
});
}
var dispose = function () {
if (reaction.current && !reaction.current.isDisposed) {
reaction.current.dispose();
}
};
React.useDebugValue(reaction, printDebugValue);
useUnmount(function () {
dispose();
});
// render the original component, but have the
// reaction track the observables, so that rendering
// can be invalidated (see above) once a dependency changes
var rendering;
var exception;
reaction.current.track(function () {
try {
rendering = fn();
}
catch (e) {
exception = e;
}
});
if (exception) {
dispose();
throw exception; // re-throw any exceptions catched during rendering
}
return rendering;
}
// n.b. base case is not used for actual typings or exported in the typing files
function observer(baseComponent, options) {
// The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307
if (isUsingStaticRendering()) {
return baseComponent;
}
var realOptions = __assign({ forwardRef: false }, options);
var baseComponentName = baseComponent.displayName || baseComponent.name;
var wrappedComponent = function (props, ref) {
return useObserver(function () { return baseComponent(props, ref); }, baseComponentName);
};
wrappedComponent.displayName = baseComponentName;
// memo; we are not intested in deep updates
// in props; we assume that if deep objects are changed,
// this is in observables, which would have been tracked anyway
var memoComponent;
if (realOptions.forwardRef) {
// we have to use forwardRef here because:
// 1. it cannot go before memo, only after it
// 2. forwardRef converts the function into an actual component, so we can't let the baseComponent do it
// since it wouldn't be a callable function anymore
memoComponent = React.memo(React.forwardRef(wrappedComponent));
}
else {
memoComponent = React.memo(wrappedComponent);
}
copyStaticProperties(baseComponent, memoComponent);
memoComponent.displayName = baseComponentName;
return memoComponent;
}
// based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js
var hoistBlackList = {
$$typeof: true,
render: true,
compare: true,
type: true
};
function copyStaticProperties(base, target) {
Object.keys(base).forEach(function (key) {
if (base.hasOwnProperty(key) && !hoistBlackList[key]) {
Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key));
}
});
}
function ObserverComponent(_a) {
var children = _a.children, render = _a.render;
var component = children || render;
if (typeof component !== "function") {
return null;
}
return useObserver(component);
}
ObserverComponent.propTypes = {
children: ObserverPropsCheck,
render: ObserverPropsCheck
};
ObserverComponent.displayName = "Observer";
function ObserverPropsCheck(props, key, componentName, location, propFullName) {
var extraKey = key === "children" ? "render" : "children";
var hasProp = typeof props[key] === "function";
var hasExtraProp = typeof props[extraKey] === "function";
if (hasProp && hasExtraProp) {
return new Error("MobX Observer: Do not use children and render in the same time in`" + componentName);
}
if (hasProp || hasExtraProp) {
return null;
}
return new Error("Invalid prop `" +
propFullName +
"` of type `" +
typeof props[key] +
"` supplied to" +
" `" +
componentName +
"`, expected `function`.");
}
function useAsObservableSourceInternal(current, usedByLocalStore) {
if (usedByLocalStore && current === undefined) {
return undefined;
}
var _a = __read(React__default.useState(function () { return mobx.observable(current, {}, { deep: false }); }), 1), res = _a[0];
mobx.runInAction(function () {
Object.assign(res, current);
});
return res;
}
function useAsObservableSource(current) {
return useAsObservableSourceInternal(current, false);
}
function useLocalStore(initializer, current) {
var source = useAsObservableSourceInternal(current, true);
return React__default.useState(function () {
var local = mobx.observable(initializer(source));
if (isPlainObject(local)) {
mobx.runInAction(function () {
Object.keys(local).forEach(function (key) {
var value = local[key];
if (typeof value === "function") {
local[key] = wrapInTransaction(value, local);
}
});
});
}
return local;
})[0];
}
// tslint:disable-next-line: ban-types
function wrapInTransaction(fn, context) {
return function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
return mobx.transaction(function () { return fn.apply(context, args); });
};
}
exports.Observer = ObserverComponent;
exports.isUsingStaticRendering = isUsingStaticRendering;
exports.observer = observer;
exports.useAsObservableSource = useAsObservableSource;
exports.useComputed = useComputed;
exports.useDisposable = useDisposable;
exports.useForceUpdate = useForceUpdate;
exports.useLocalStore = useLocalStore;
exports.useObservable = useObservable;
exports.useObserver = useObserver;
exports.useStaticRendering = useStaticRendering;
Object.defineProperty(exports, '__esModule', { value: true });
}));