UNPKG

22.7 kBJavaScriptView Raw
1(function (global, factory) {
2 typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('mobx'), require('react'), require('react-dom')) :
3 typeof define === 'function' && define.amd ? define(['exports', 'mobx', 'react', 'react-dom'], factory) :
4 (global = global || self, factory(global.mobxReactLite = {}, global.mobx, global.React, global.ReactDOM));
5}(this, (function (exports, mobx, React, reactDom) { 'use strict';
6
7 var React__default = 'default' in React ? React['default'] : React;
8
9 if (!React.useState) {
10 throw new Error("mobx-react-lite requires React with Hooks support");
11 }
12
13 if (!mobx.makeObservable) {
14 throw new Error("mobx-react-lite@3 requires mobx at least version 6 to be available");
15 }
16
17 function defaultNoopBatch(callback) {
18 callback();
19 }
20 function observerBatching(reactionScheduler) {
21 if (!reactionScheduler) {
22 reactionScheduler = defaultNoopBatch;
23
24 {
25 console.warn("[MobX] Failed to get unstable_batched updates from react-dom / react-native");
26 }
27 }
28
29 mobx.configure({
30 reactionScheduler: reactionScheduler
31 });
32 }
33 var isObserverBatched = function isObserverBatched() {
34 {
35 console.warn("[MobX] Deprecated");
36 }
37
38 return true;
39 };
40
41 var deprecatedMessages = [];
42 function useDeprecated(msg) {
43 if (!deprecatedMessages.includes(msg)) {
44 deprecatedMessages.push(msg);
45 console.warn(msg);
46 }
47 }
48
49 function printDebugValue(v) {
50 return mobx.getDependencyTree(v);
51 }
52
53 var FinalizationRegistryLocal = typeof FinalizationRegistry === "undefined" ? undefined : FinalizationRegistry;
54
55 function createTrackingData(reaction) {
56 var trackingData = {
57 reaction: reaction,
58 mounted: false,
59 changedBeforeMount: false,
60 cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS
61 };
62 return trackingData;
63 }
64 /**
65 * The minimum time before we'll clean up a Reaction created in a render
66 * for a component that hasn't managed to run its effects. This needs to
67 * be big enough to ensure that a component won't turn up and have its
68 * effects run without being re-rendered.
69 */
70
71 var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000;
72 /**
73 * The frequency with which we'll check for leaked reactions.
74 */
75
76 var CLEANUP_TIMER_LOOP_MILLIS = 10000;
77
78 /**
79 * FinalizationRegistry-based uncommitted reaction cleanup
80 */
81
82 function createReactionCleanupTrackingUsingFinalizationRegister(FinalizationRegistry) {
83 var cleanupTokenToReactionTrackingMap = new Map();
84 var globalCleanupTokensCounter = 1;
85 var registry = new FinalizationRegistry(function cleanupFunction(token) {
86 var trackedReaction = cleanupTokenToReactionTrackingMap.get(token);
87
88 if (trackedReaction) {
89 trackedReaction.reaction.dispose();
90 cleanupTokenToReactionTrackingMap["delete"](token);
91 }
92 });
93 return {
94 addReactionToTrack: function addReactionToTrack(reactionTrackingRef, reaction, objectRetainedByReact) {
95 var token = globalCleanupTokensCounter++;
96 registry.register(objectRetainedByReact, token, reactionTrackingRef);
97 reactionTrackingRef.current = createTrackingData(reaction);
98 reactionTrackingRef.current.finalizationRegistryCleanupToken = token;
99 cleanupTokenToReactionTrackingMap.set(token, reactionTrackingRef.current);
100 return reactionTrackingRef.current;
101 },
102 recordReactionAsCommitted: function recordReactionAsCommitted(reactionRef) {
103 registry.unregister(reactionRef);
104
105 if (reactionRef.current && reactionRef.current.finalizationRegistryCleanupToken) {
106 cleanupTokenToReactionTrackingMap["delete"](reactionRef.current.finalizationRegistryCleanupToken);
107 }
108 },
109 forceCleanupTimerToRunNowForTests: function forceCleanupTimerToRunNowForTests() {// When FinalizationRegistry in use, this this is no-op
110 },
111 resetCleanupScheduleForTests: function resetCleanupScheduleForTests() {// When FinalizationRegistry in use, this this is no-op
112 }
113 };
114 }
115
116 function _unsupportedIterableToArray(o, minLen) {
117 if (!o) return;
118 if (typeof o === "string") return _arrayLikeToArray(o, minLen);
119 var n = Object.prototype.toString.call(o).slice(8, -1);
120 if (n === "Object" && o.constructor) n = o.constructor.name;
121 if (n === "Map" || n === "Set") return Array.from(o);
122 if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
123 }
124
125 function _arrayLikeToArray(arr, len) {
126 if (len == null || len > arr.length) len = arr.length;
127
128 for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];
129
130 return arr2;
131 }
132
133 function _createForOfIteratorHelperLoose(o, allowArrayLike) {
134 var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];
135 if (it) return (it = it.call(o)).next.bind(it);
136
137 if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
138 if (it) o = it;
139 var i = 0;
140 return function () {
141 if (i >= o.length) return {
142 done: true
143 };
144 return {
145 done: false,
146 value: o[i++]
147 };
148 };
149 }
150
151 throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
152 }
153
154 /**
155 * timers, gc-style, uncommitted reaction cleanup
156 */
157
158 function createTimerBasedReactionCleanupTracking() {
159 /**
160 * Reactions created by components that have yet to be fully mounted.
161 */
162 var uncommittedReactionRefs = new Set();
163 /**
164 * Latest 'uncommitted reactions' cleanup timer handle.
165 */
166
167 var reactionCleanupHandle;
168 /* istanbul ignore next */
169
170 /**
171 * Only to be used by test functions; do not export outside of mobx-react-lite
172 */
173
174 function forceCleanupTimerToRunNowForTests() {
175 // This allows us to control the execution of the cleanup timer
176 // to force it to run at awkward times in unit tests.
177 if (reactionCleanupHandle) {
178 clearTimeout(reactionCleanupHandle);
179 cleanUncommittedReactions();
180 }
181 }
182 /* istanbul ignore next */
183
184
185 function resetCleanupScheduleForTests() {
186 if (uncommittedReactionRefs.size > 0) {
187 for (var _iterator = _createForOfIteratorHelperLoose(uncommittedReactionRefs), _step; !(_step = _iterator()).done;) {
188 var ref = _step.value;
189 var tracking = ref.current;
190
191 if (tracking) {
192 tracking.reaction.dispose();
193 ref.current = null;
194 }
195 }
196
197 uncommittedReactionRefs.clear();
198 }
199
200 if (reactionCleanupHandle) {
201 clearTimeout(reactionCleanupHandle);
202 reactionCleanupHandle = undefined;
203 }
204 }
205
206 function ensureCleanupTimerRunning() {
207 if (reactionCleanupHandle === undefined) {
208 reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS);
209 }
210 }
211
212 function scheduleCleanupOfReactionIfLeaked(ref) {
213 uncommittedReactionRefs.add(ref);
214 ensureCleanupTimerRunning();
215 }
216
217 function recordReactionAsCommitted(reactionRef) {
218 uncommittedReactionRefs["delete"](reactionRef);
219 }
220 /**
221 * Run by the cleanup timer to dispose any outstanding reactions
222 */
223
224
225 function cleanUncommittedReactions() {
226 reactionCleanupHandle = undefined; // Loop through all the candidate leaked reactions; those older
227 // than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied.
228
229 var now = Date.now();
230 uncommittedReactionRefs.forEach(function (ref) {
231 var tracking = ref.current;
232
233 if (tracking) {
234 if (now >= tracking.cleanAt) {
235 // It's time to tidy up this leaked reaction.
236 tracking.reaction.dispose();
237 ref.current = null;
238 uncommittedReactionRefs["delete"](ref);
239 }
240 }
241 });
242
243 if (uncommittedReactionRefs.size > 0) {
244 // We've just finished a round of cleanups but there are still
245 // some leak candidates outstanding.
246 ensureCleanupTimerRunning();
247 }
248 }
249
250 return {
251 addReactionToTrack: function addReactionToTrack(reactionTrackingRef, reaction,
252 /**
253 * On timer based implementation we don't really need this object,
254 * but we keep the same api
255 */
256 objectRetainedByReact) {
257 reactionTrackingRef.current = createTrackingData(reaction);
258 scheduleCleanupOfReactionIfLeaked(reactionTrackingRef);
259 return reactionTrackingRef.current;
260 },
261 recordReactionAsCommitted: recordReactionAsCommitted,
262 forceCleanupTimerToRunNowForTests: forceCleanupTimerToRunNowForTests,
263 resetCleanupScheduleForTests: resetCleanupScheduleForTests
264 };
265 }
266
267 var _ref = FinalizationRegistryLocal ? /*#__PURE__*/createReactionCleanupTrackingUsingFinalizationRegister(FinalizationRegistryLocal) : /*#__PURE__*/createTimerBasedReactionCleanupTracking(),
268 addReactionToTrack = _ref.addReactionToTrack,
269 recordReactionAsCommitted = _ref.recordReactionAsCommitted,
270 resetCleanupScheduleForTests = _ref.resetCleanupScheduleForTests;
271
272 var globalIsUsingStaticRendering = false;
273 function enableStaticRendering(enable) {
274 globalIsUsingStaticRendering = enable;
275 }
276 function isUsingStaticRendering() {
277 return globalIsUsingStaticRendering;
278 }
279
280 function observerComponentNameFor(baseComponentName) {
281 return "observer" + baseComponentName;
282 }
283 /**
284 * We use class to make it easier to detect in heap snapshots by name
285 */
286
287
288 var ObjectToBeRetainedByReact = function ObjectToBeRetainedByReact() {};
289
290 function objectToBeRetainedByReactFactory() {
291 return new ObjectToBeRetainedByReact();
292 }
293
294 function useObserver(fn, baseComponentName) {
295 if (baseComponentName === void 0) {
296 baseComponentName = "observed";
297 }
298
299 if (isUsingStaticRendering()) {
300 return fn();
301 }
302
303 var _React$useState = React__default.useState(objectToBeRetainedByReactFactory),
304 objectRetainedByReact = _React$useState[0]; // Force update, see #2982
305
306
307 var _React$useState2 = React__default.useState(),
308 setState = _React$useState2[1];
309
310 var forceUpdate = function forceUpdate() {
311 return setState([]);
312 }; // StrictMode/ConcurrentMode/Suspense may mean that our component is
313 // rendered and abandoned multiple times, so we need to track leaked
314 // Reactions.
315
316
317 var reactionTrackingRef = React__default.useRef(null);
318
319 if (!reactionTrackingRef.current) {
320 // First render for this component (or first time since a previous
321 // reaction from an abandoned render was disposed).
322 var newReaction = new mobx.Reaction(observerComponentNameFor(baseComponentName), function () {
323 // Observable has changed, meaning we want to re-render
324 // BUT if we're a component that hasn't yet got to the useEffect()
325 // stage, we might be a component that _started_ to render, but
326 // got dropped, and we don't want to make state changes then.
327 // (It triggers warnings in StrictMode, for a start.)
328 if (trackingData.mounted) {
329 // We have reached useEffect(), so we're mounted, and can trigger an update
330 forceUpdate();
331 } else {
332 // We haven't yet reached useEffect(), so we'll need to trigger a re-render
333 // when (and if) useEffect() arrives.
334 trackingData.changedBeforeMount = true;
335 }
336 });
337 var trackingData = addReactionToTrack(reactionTrackingRef, newReaction, objectRetainedByReact);
338 }
339
340 var reaction = reactionTrackingRef.current.reaction;
341 React__default.useDebugValue(reaction, printDebugValue);
342 React__default.useEffect(function () {
343 // Called on first mount only
344 recordReactionAsCommitted(reactionTrackingRef);
345
346 if (reactionTrackingRef.current) {
347 // Great. We've already got our reaction from our render;
348 // all we need to do is to record that it's now mounted,
349 // to allow future observable changes to trigger re-renders
350 reactionTrackingRef.current.mounted = true; // Got a change before first mount, force an update
351
352 if (reactionTrackingRef.current.changedBeforeMount) {
353 reactionTrackingRef.current.changedBeforeMount = false;
354 forceUpdate();
355 }
356 } else {
357 // The reaction we set up in our render has been disposed.
358 // This can be due to bad timings of renderings, e.g. our
359 // component was paused for a _very_ long time, and our
360 // reaction got cleaned up
361 // Re-create the reaction
362 reactionTrackingRef.current = {
363 reaction: new mobx.Reaction(observerComponentNameFor(baseComponentName), function () {
364 // We've definitely already been mounted at this point
365 forceUpdate();
366 }),
367 mounted: true,
368 changedBeforeMount: false,
369 cleanAt: Infinity
370 };
371 forceUpdate();
372 }
373
374 return function () {
375 reactionTrackingRef.current.reaction.dispose();
376 reactionTrackingRef.current = null;
377 };
378 }, []); // render the original component, but have the
379 // reaction track the observables, so that rendering
380 // can be invalidated (see above) once a dependency changes
381
382 var rendering;
383 var exception;
384 reaction.track(function () {
385 try {
386 rendering = fn();
387 } catch (e) {
388 exception = e;
389 }
390 });
391
392 if (exception) {
393 throw exception; // re-throw any exceptions caught during rendering
394 }
395
396 return rendering;
397 }
398
399 var warnObserverOptionsDeprecated = true;
400 var hasSymbol = typeof Symbol === "function" && Symbol["for"]; // Using react-is had some issues (and operates on elements, not on types), see #608 / #609
401
402 var ReactForwardRefSymbol = hasSymbol ? /*#__PURE__*/Symbol["for"]("react.forward_ref") : typeof React.forwardRef === "function" && /*#__PURE__*/React.forwardRef(function (props) {
403 return null;
404 })["$$typeof"];
405 var ReactMemoSymbol = hasSymbol ? /*#__PURE__*/Symbol["for"]("react.memo") : typeof React.memo === "function" && /*#__PURE__*/React.memo(function (props) {
406 return null;
407 })["$$typeof"]; // n.b. base case is not used for actual typings or exported in the typing files
408
409 function observer(baseComponent, // TODO remove in next major
410 options) {
411 var _options$forwardRef;
412
413 if ( warnObserverOptionsDeprecated && options) {
414 warnObserverOptionsDeprecated = false;
415 console.warn("[mobx-react-lite] `observer(fn, { forwardRef: true })` is deprecated, use `observer(React.forwardRef(fn))`");
416 }
417
418 if (ReactMemoSymbol && baseComponent["$$typeof"] === ReactMemoSymbol) {
419 throw new Error("[mobx-react-lite] You are trying to use `observer` on a function component wrapped in either another `observer` or `React.memo`. The observer already applies 'React.memo' for you.");
420 } // The working of observer is explained step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307
421
422
423 if (isUsingStaticRendering()) {
424 return baseComponent;
425 }
426
427 var useForwardRef = (_options$forwardRef = options == null ? void 0 : options.forwardRef) != null ? _options$forwardRef : false;
428 var render = baseComponent;
429 var baseComponentName = baseComponent.displayName || baseComponent.name; // If already wrapped with forwardRef, unwrap,
430 // so we can patch render and apply memo
431
432 if (ReactForwardRefSymbol && baseComponent["$$typeof"] === ReactForwardRefSymbol) {
433 useForwardRef = true;
434 render = baseComponent["render"];
435
436 if (typeof render !== "function") {
437 throw new Error("[mobx-react-lite] `render` property of ForwardRef was not a function");
438 }
439 }
440
441 var observerComponent = function observerComponent(props, ref) {
442 return useObserver(function () {
443 return render(props, ref);
444 }, baseComponentName);
445 }; // Don't set `displayName` for anonymous components,
446 // so the `displayName` can be customized by user, see #3192.
447
448
449 if (baseComponentName !== "") {
450 observerComponent.displayName = baseComponentName;
451 } // Support legacy context: `contextTypes` must be applied before `memo`
452
453
454 if (baseComponent.contextTypes) {
455 observerComponent.contextTypes = baseComponent.contextTypes;
456 }
457
458 if (useForwardRef) {
459 // `forwardRef` must be applied prior `memo`
460 // `forwardRef(observer(cmp))` throws:
461 // "forwardRef requires a render function but received a `memo` component. Instead of forwardRef(memo(...)), use memo(forwardRef(...))"
462 observerComponent = React.forwardRef(observerComponent);
463 } // memo; we are not interested in deep updates
464 // in props; we assume that if deep objects are changed,
465 // this is in observables, which would have been tracked anyway
466
467
468 observerComponent = React.memo(observerComponent);
469 copyStaticProperties(baseComponent, observerComponent);
470
471 {
472 Object.defineProperty(observerComponent, "contextTypes", {
473 set: function set() {
474 var _this$type;
475
476 throw new Error("[mobx-react-lite] `" + (this.displayName || ((_this$type = this.type) == null ? void 0 : _this$type.displayName) || "Component") + ".contextTypes` must be set before applying `observer`.");
477 }
478 });
479 }
480
481 return observerComponent;
482 } // based on https://github.com/mridgway/hoist-non-react-statics/blob/master/src/index.js
483
484 var hoistBlackList = {
485 $$typeof: true,
486 render: true,
487 compare: true,
488 type: true,
489 // Don't redefine `displayName`,
490 // it's defined as getter-setter pair on `memo` (see #3192).
491 displayName: true
492 };
493
494 function copyStaticProperties(base, target) {
495 Object.keys(base).forEach(function (key) {
496 if (!hoistBlackList[key]) {
497 Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(base, key));
498 }
499 });
500 }
501
502 function ObserverComponent(_ref) {
503 var children = _ref.children,
504 render = _ref.render;
505 var component = children || render;
506
507 if (typeof component !== "function") {
508 return null;
509 }
510
511 return useObserver(component);
512 }
513
514 {
515 ObserverComponent.propTypes = {
516 children: ObserverPropsCheck,
517 render: ObserverPropsCheck
518 };
519 }
520
521 ObserverComponent.displayName = "Observer";
522
523 function ObserverPropsCheck(props, key, componentName, location, propFullName) {
524 var extraKey = key === "children" ? "render" : "children";
525 var hasProp = typeof props[key] === "function";
526 var hasExtraProp = typeof props[extraKey] === "function";
527
528 if (hasProp && hasExtraProp) {
529 return new Error("MobX Observer: Do not use children and render in the same time in`" + componentName);
530 }
531
532 if (hasProp || hasExtraProp) {
533 return null;
534 }
535
536 return new Error("Invalid prop `" + propFullName + "` of type `" + typeof props[key] + "` supplied to" + " `" + componentName + "`, expected `function`.");
537 }
538
539 function useLocalObservable(initializer, annotations) {
540 return React.useState(function () {
541 return mobx.observable(initializer(), annotations, {
542 autoBind: true
543 });
544 })[0];
545 }
546
547 function useAsObservableSource(current) {
548 useDeprecated("[mobx-react-lite] 'useAsObservableSource' is deprecated, please store the values directly in an observable, for example by using 'useLocalObservable', and sync future updates using 'useEffect' when needed. See the README for examples.");
549
550 var _useState = React.useState(function () {
551 return mobx.observable(current, {}, {
552 deep: false
553 });
554 }),
555 res = _useState[0];
556
557 mobx.runInAction(function () {
558 Object.assign(res, current);
559 });
560 return res;
561 }
562
563 function useLocalStore(initializer, current) {
564 useDeprecated("[mobx-react-lite] 'useLocalStore' is deprecated, use 'useLocalObservable' instead.");
565 var source = current && useAsObservableSource(current);
566 return React.useState(function () {
567 return mobx.observable(initializer(source), undefined, {
568 autoBind: true
569 });
570 })[0];
571 }
572
573 observerBatching(reactDom.unstable_batchedUpdates);
574 function useObserver$1(fn, baseComponentName) {
575 if (baseComponentName === void 0) {
576 baseComponentName = "observed";
577 }
578
579 {
580 useDeprecated("[mobx-react-lite] 'useObserver(fn)' is deprecated. Use `<Observer>{fn}</Observer>` instead, or wrap the entire component in `observer`.");
581 }
582
583 return useObserver(fn, baseComponentName);
584 }
585 function useStaticRendering(enable) {
586 {
587 console.warn("[mobx-react-lite] 'useStaticRendering' is deprecated, use 'enableStaticRendering' instead");
588 }
589
590 enableStaticRendering(enable);
591 }
592
593 exports.Observer = ObserverComponent;
594 exports.clearTimers = resetCleanupScheduleForTests;
595 exports.enableStaticRendering = enableStaticRendering;
596 exports.isObserverBatched = isObserverBatched;
597 exports.isUsingStaticRendering = isUsingStaticRendering;
598 exports.observer = observer;
599 exports.observerBatching = observerBatching;
600 exports.useAsObservableSource = useAsObservableSource;
601 exports.useLocalObservable = useLocalObservable;
602 exports.useLocalStore = useLocalStore;
603 exports.useObserver = useObserver$1;
604 exports.useStaticRendering = useStaticRendering;
605
606 Object.defineProperty(exports, '__esModule', { value: true });
607
608})));
609//# sourceMappingURL=mobxreactlite.umd.development.js.map