UNPKG

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