mobx-react-lite
Advanced tools
Comparing version 1.2.0 to 2.0.0-alpha.0
@@ -112,2 +112,13 @@ (function (global, factory) { | ||
function __values(o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
} | ||
function __read(o, n) { | ||
@@ -131,12 +142,80 @@ var m = typeof Symbol === "function" && o[Symbol.iterator]; | ||
function printDebugValue(v) { | ||
if (!v.current) { | ||
return "<unknown>"; | ||
return mobx.getDependencyTree(v); | ||
} | ||
function createTrackingData(reaction) { | ||
var trackingData = { | ||
cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS, | ||
reaction: reaction | ||
}; | ||
return trackingData; | ||
} | ||
/** | ||
* The minimum time before we'll clean up a Reaction created in a render | ||
* for a component that hasn't managed to run its effects. This needs to | ||
* be big enough to ensure that a component won't turn up and have its | ||
* effects run without being re-rendered. | ||
*/ | ||
var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000; | ||
/** | ||
* The frequency with which we'll check for leaked reactions. | ||
*/ | ||
var CLEANUP_TIMER_LOOP_MILLIS = 10000; | ||
/** | ||
* Reactions created by components that have yet to be fully mounted. | ||
*/ | ||
var uncommittedReactionRefs = new Set(); | ||
/** | ||
* Latest 'uncommitted reactions' cleanup timer handle. | ||
*/ | ||
var reactionCleanupHandle; | ||
function ensureCleanupTimerRunning() { | ||
if (reactionCleanupHandle === undefined) { | ||
reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS); | ||
} | ||
return mobx.getDependencyTree(v.current); | ||
} | ||
function scheduleCleanupOfReactionIfLeaked(ref) { | ||
uncommittedReactionRefs.add(ref); | ||
ensureCleanupTimerRunning(); | ||
} | ||
function recordReactionAsCommitted(reactionRef) { | ||
uncommittedReactionRefs.delete(reactionRef); | ||
} | ||
/** | ||
* Run by the cleanup timer to dispose any outstanding reactions | ||
*/ | ||
function cleanUncommittedReactions() { | ||
var e_1, _a; | ||
reactionCleanupHandle = undefined; | ||
// Loop through all the candidate leaked reactions; those older | ||
// than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied. | ||
var now = Date.now(); | ||
try { | ||
for (var uncommittedReactionRefs_1 = __values(uncommittedReactionRefs), uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next(); !uncommittedReactionRefs_1_1.done; uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next()) { | ||
var ref = uncommittedReactionRefs_1_1.value; | ||
var tracking = ref.current; | ||
if (tracking) { | ||
if (now >= tracking.cleanAt) { | ||
// It's time to tidy up this leaked reaction. | ||
tracking.reaction.dispose(); | ||
ref.current = null; | ||
uncommittedReactionRefs.delete(ref); | ||
} | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (uncommittedReactionRefs_1_1 && !uncommittedReactionRefs_1_1.done && (_a = uncommittedReactionRefs_1.return)) _a.call(uncommittedReactionRefs_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
if (uncommittedReactionRefs.size > 0) { | ||
// We've just finished a round of cleanups but there are still | ||
// some leak candidates outstanding. | ||
ensureCleanupTimerRunning(); | ||
} | ||
} | ||
var EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
react.useEffect(function () { return fn; }, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
@@ -151,2 +230,5 @@ var _a = __read(react.useState(0), 2), setTick = _a[1]; | ||
var EMPTY_OBJECT = {}; | ||
function observerComponentNameFor(baseComponentName) { | ||
return "observer" + baseComponentName; | ||
} | ||
function useObserver(fn, baseComponentName, options) { | ||
@@ -160,12 +242,60 @@ if (baseComponentName === void 0) { baseComponentName = "observed"; } | ||
var forceUpdate = wantedForceUpdateHook(); | ||
var reaction = react.useRef(null); | ||
if (!reaction.current) { | ||
reaction.current = new mobx.Reaction("observer(" + baseComponentName + ")", function () { | ||
forceUpdate(); | ||
// StrictMode/ConcurrentMode/Suspense may mean that our component is | ||
// rendered and abandoned multiple times, so we need to track leaked | ||
// Reactions. | ||
var reactionTrackingRef = react.useRef(null); | ||
if (!reactionTrackingRef.current) { | ||
// First render for this component (or first time since a previous | ||
// reaction from an abandoned render was disposed). | ||
var newReaction_1 = new mobx.Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// Observable has changed, meaning we want to re-render | ||
// BUT if we're a component that hasn't yet got to the useEffect() | ||
// stage, we might be a component that _started_ to render, but | ||
// got dropped, and we don't want to make state changes then. | ||
// (It triggers warnings in StrictMode, for a start.) | ||
if (trackingData_1.mounted) { | ||
// We have reached useEffect(), so we're mounted, and can trigger an update | ||
forceUpdate(); | ||
} | ||
else { | ||
// We haven't yet reached useEffect(), so we'll need to trigger a re-render | ||
// when (and if) useEffect() arrives. The easiest way to do that is just to | ||
// drop our current reaction and allow useEffect() to recreate it. | ||
newReaction_1.dispose(); | ||
reactionTrackingRef.current = null; | ||
} | ||
}); | ||
var trackingData_1 = createTrackingData(newReaction_1); | ||
reactionTrackingRef.current = trackingData_1; | ||
scheduleCleanupOfReactionIfLeaked(reactionTrackingRef); | ||
} | ||
var reaction = reactionTrackingRef.current.reaction; | ||
react.useDebugValue(reaction, printDebugValue); | ||
useUnmount(function () { | ||
reaction.current.dispose(); | ||
}); | ||
react.useEffect(function () { | ||
// Called on first mount only | ||
recordReactionAsCommitted(reactionTrackingRef); | ||
if (reactionTrackingRef.current) { | ||
// Great. We've already got our reaction from our render; | ||
// all we need to do is to record that it's now mounted, | ||
// to allow future observable changes to trigger re-renders | ||
reactionTrackingRef.current.mounted = true; | ||
} | ||
else { | ||
// The reaction we set up in our render has been disposed. | ||
// This is either due to bad timings of renderings, e.g. our | ||
// component was paused for a _very_ long time, and our | ||
// reaction got cleaned up, or we got a observable change | ||
// between render and useEffect | ||
// Re-create the reaction | ||
reactionTrackingRef.current = { | ||
reaction: new mobx.Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// We've definitely already been mounted at this point | ||
forceUpdate(); | ||
}), | ||
cleanAt: Infinity | ||
}; | ||
forceUpdate(); | ||
} | ||
return function () { return reactionTrackingRef.current.reaction.dispose(); }; | ||
}, []); | ||
// render the original component, but have the | ||
@@ -176,3 +306,3 @@ // reaction track the observables, so that rendering | ||
var exception; | ||
reaction.current.track(function () { | ||
reaction.track(function () { | ||
try { | ||
@@ -186,3 +316,2 @@ rendering = fn(); | ||
if (exception) { | ||
reaction.current.dispose(); | ||
throw exception; // re-throw any exceptions catched during rendering | ||
@@ -195,3 +324,3 @@ } | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
// 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()) { | ||
@@ -272,11 +401,11 @@ return baseComponent; | ||
exports.useObservable = useObservable; | ||
exports.Observer = ObserverComponent; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.observer = observer; | ||
exports.useComputed = useComputed; | ||
exports.useDisposable = useDisposable; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.useForceUpdate = useForceUpdate; | ||
exports.useObservable = useObservable; | ||
exports.useObserver = useObserver; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.observer = observer; | ||
exports.useObserver = useObserver; | ||
exports.Observer = ObserverComponent; | ||
exports.useForceUpdate = useForceUpdate; | ||
@@ -283,0 +412,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -109,2 +109,13 @@ import { spy, observable, computed, getDependencyTree, Reaction } from 'mobx'; | ||
function __values(o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
} | ||
function __read(o, n) { | ||
@@ -128,12 +139,80 @@ var m = typeof Symbol === "function" && o[Symbol.iterator]; | ||
function printDebugValue(v) { | ||
if (!v.current) { | ||
return "<unknown>"; | ||
return getDependencyTree(v); | ||
} | ||
function createTrackingData(reaction) { | ||
var trackingData = { | ||
cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS, | ||
reaction: reaction | ||
}; | ||
return trackingData; | ||
} | ||
/** | ||
* The minimum time before we'll clean up a Reaction created in a render | ||
* for a component that hasn't managed to run its effects. This needs to | ||
* be big enough to ensure that a component won't turn up and have its | ||
* effects run without being re-rendered. | ||
*/ | ||
var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000; | ||
/** | ||
* The frequency with which we'll check for leaked reactions. | ||
*/ | ||
var CLEANUP_TIMER_LOOP_MILLIS = 10000; | ||
/** | ||
* Reactions created by components that have yet to be fully mounted. | ||
*/ | ||
var uncommittedReactionRefs = new Set(); | ||
/** | ||
* Latest 'uncommitted reactions' cleanup timer handle. | ||
*/ | ||
var reactionCleanupHandle; | ||
function ensureCleanupTimerRunning() { | ||
if (reactionCleanupHandle === undefined) { | ||
reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS); | ||
} | ||
return getDependencyTree(v.current); | ||
} | ||
function scheduleCleanupOfReactionIfLeaked(ref) { | ||
uncommittedReactionRefs.add(ref); | ||
ensureCleanupTimerRunning(); | ||
} | ||
function recordReactionAsCommitted(reactionRef) { | ||
uncommittedReactionRefs.delete(reactionRef); | ||
} | ||
/** | ||
* Run by the cleanup timer to dispose any outstanding reactions | ||
*/ | ||
function cleanUncommittedReactions() { | ||
var e_1, _a; | ||
reactionCleanupHandle = undefined; | ||
// Loop through all the candidate leaked reactions; those older | ||
// than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied. | ||
var now = Date.now(); | ||
try { | ||
for (var uncommittedReactionRefs_1 = __values(uncommittedReactionRefs), uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next(); !uncommittedReactionRefs_1_1.done; uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next()) { | ||
var ref = uncommittedReactionRefs_1_1.value; | ||
var tracking = ref.current; | ||
if (tracking) { | ||
if (now >= tracking.cleanAt) { | ||
// It's time to tidy up this leaked reaction. | ||
tracking.reaction.dispose(); | ||
ref.current = null; | ||
uncommittedReactionRefs.delete(ref); | ||
} | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (uncommittedReactionRefs_1_1 && !uncommittedReactionRefs_1_1.done && (_a = uncommittedReactionRefs_1.return)) _a.call(uncommittedReactionRefs_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
if (uncommittedReactionRefs.size > 0) { | ||
// We've just finished a round of cleanups but there are still | ||
// some leak candidates outstanding. | ||
ensureCleanupTimerRunning(); | ||
} | ||
} | ||
var EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
useEffect(function () { return fn; }, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
@@ -148,2 +227,5 @@ var _a = __read(useState(0), 2), setTick = _a[1]; | ||
var EMPTY_OBJECT = {}; | ||
function observerComponentNameFor(baseComponentName) { | ||
return "observer" + baseComponentName; | ||
} | ||
function useObserver(fn, baseComponentName, options) { | ||
@@ -157,12 +239,60 @@ if (baseComponentName === void 0) { baseComponentName = "observed"; } | ||
var forceUpdate = wantedForceUpdateHook(); | ||
var reaction = useRef(null); | ||
if (!reaction.current) { | ||
reaction.current = new Reaction("observer(" + baseComponentName + ")", function () { | ||
forceUpdate(); | ||
// StrictMode/ConcurrentMode/Suspense may mean that our component is | ||
// rendered and abandoned multiple times, so we need to track leaked | ||
// Reactions. | ||
var reactionTrackingRef = useRef(null); | ||
if (!reactionTrackingRef.current) { | ||
// First render for this component (or first time since a previous | ||
// reaction from an abandoned render was disposed). | ||
var newReaction_1 = new Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// Observable has changed, meaning we want to re-render | ||
// BUT if we're a component that hasn't yet got to the useEffect() | ||
// stage, we might be a component that _started_ to render, but | ||
// got dropped, and we don't want to make state changes then. | ||
// (It triggers warnings in StrictMode, for a start.) | ||
if (trackingData_1.mounted) { | ||
// We have reached useEffect(), so we're mounted, and can trigger an update | ||
forceUpdate(); | ||
} | ||
else { | ||
// We haven't yet reached useEffect(), so we'll need to trigger a re-render | ||
// when (and if) useEffect() arrives. The easiest way to do that is just to | ||
// drop our current reaction and allow useEffect() to recreate it. | ||
newReaction_1.dispose(); | ||
reactionTrackingRef.current = null; | ||
} | ||
}); | ||
var trackingData_1 = createTrackingData(newReaction_1); | ||
reactionTrackingRef.current = trackingData_1; | ||
scheduleCleanupOfReactionIfLeaked(reactionTrackingRef); | ||
} | ||
var reaction = reactionTrackingRef.current.reaction; | ||
useDebugValue(reaction, printDebugValue); | ||
useUnmount(function () { | ||
reaction.current.dispose(); | ||
}); | ||
useEffect(function () { | ||
// Called on first mount only | ||
recordReactionAsCommitted(reactionTrackingRef); | ||
if (reactionTrackingRef.current) { | ||
// Great. We've already got our reaction from our render; | ||
// all we need to do is to record that it's now mounted, | ||
// to allow future observable changes to trigger re-renders | ||
reactionTrackingRef.current.mounted = true; | ||
} | ||
else { | ||
// The reaction we set up in our render has been disposed. | ||
// This is either due to bad timings of renderings, e.g. our | ||
// component was paused for a _very_ long time, and our | ||
// reaction got cleaned up, or we got a observable change | ||
// between render and useEffect | ||
// Re-create the reaction | ||
reactionTrackingRef.current = { | ||
reaction: new Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// We've definitely already been mounted at this point | ||
forceUpdate(); | ||
}), | ||
cleanAt: Infinity | ||
}; | ||
forceUpdate(); | ||
} | ||
return function () { return reactionTrackingRef.current.reaction.dispose(); }; | ||
}, []); | ||
// render the original component, but have the | ||
@@ -173,3 +303,3 @@ // reaction track the observables, so that rendering | ||
var exception; | ||
reaction.current.track(function () { | ||
reaction.track(function () { | ||
try { | ||
@@ -183,3 +313,2 @@ rendering = fn(); | ||
if (exception) { | ||
reaction.current.dispose(); | ||
throw exception; // re-throw any exceptions catched during rendering | ||
@@ -192,3 +321,3 @@ } | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
// 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()) { | ||
@@ -269,2 +398,2 @@ return baseComponent; | ||
export { useObservable, useComputed, useDisposable, isUsingStaticRendering, useStaticRendering, observer, useObserver, ObserverComponent as Observer, useForceUpdate }; | ||
export { ObserverComponent as Observer, isUsingStaticRendering, observer, useComputed, useDisposable, useForceUpdate, useObservable, useObserver, useStaticRendering }; |
@@ -112,2 +112,13 @@ (function (global, factory) { | ||
function __values(o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
} | ||
function __read(o, n) { | ||
@@ -131,12 +142,80 @@ var m = typeof Symbol === "function" && o[Symbol.iterator]; | ||
function printDebugValue(v) { | ||
if (!v.current) { | ||
return "<unknown>"; | ||
return mobx.getDependencyTree(v); | ||
} | ||
function createTrackingData(reaction) { | ||
var trackingData = { | ||
cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS, | ||
reaction: reaction | ||
}; | ||
return trackingData; | ||
} | ||
/** | ||
* The minimum time before we'll clean up a Reaction created in a render | ||
* for a component that hasn't managed to run its effects. This needs to | ||
* be big enough to ensure that a component won't turn up and have its | ||
* effects run without being re-rendered. | ||
*/ | ||
var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000; | ||
/** | ||
* The frequency with which we'll check for leaked reactions. | ||
*/ | ||
var CLEANUP_TIMER_LOOP_MILLIS = 10000; | ||
/** | ||
* Reactions created by components that have yet to be fully mounted. | ||
*/ | ||
var uncommittedReactionRefs = new Set(); | ||
/** | ||
* Latest 'uncommitted reactions' cleanup timer handle. | ||
*/ | ||
var reactionCleanupHandle; | ||
function ensureCleanupTimerRunning() { | ||
if (reactionCleanupHandle === undefined) { | ||
reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS); | ||
} | ||
return mobx.getDependencyTree(v.current); | ||
} | ||
function scheduleCleanupOfReactionIfLeaked(ref) { | ||
uncommittedReactionRefs.add(ref); | ||
ensureCleanupTimerRunning(); | ||
} | ||
function recordReactionAsCommitted(reactionRef) { | ||
uncommittedReactionRefs.delete(reactionRef); | ||
} | ||
/** | ||
* Run by the cleanup timer to dispose any outstanding reactions | ||
*/ | ||
function cleanUncommittedReactions() { | ||
var e_1, _a; | ||
reactionCleanupHandle = undefined; | ||
// Loop through all the candidate leaked reactions; those older | ||
// than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied. | ||
var now = Date.now(); | ||
try { | ||
for (var uncommittedReactionRefs_1 = __values(uncommittedReactionRefs), uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next(); !uncommittedReactionRefs_1_1.done; uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next()) { | ||
var ref = uncommittedReactionRefs_1_1.value; | ||
var tracking = ref.current; | ||
if (tracking) { | ||
if (now >= tracking.cleanAt) { | ||
// It's time to tidy up this leaked reaction. | ||
tracking.reaction.dispose(); | ||
ref.current = null; | ||
uncommittedReactionRefs.delete(ref); | ||
} | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (uncommittedReactionRefs_1_1 && !uncommittedReactionRefs_1_1.done && (_a = uncommittedReactionRefs_1.return)) _a.call(uncommittedReactionRefs_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
if (uncommittedReactionRefs.size > 0) { | ||
// We've just finished a round of cleanups but there are still | ||
// some leak candidates outstanding. | ||
ensureCleanupTimerRunning(); | ||
} | ||
} | ||
var EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
react.useEffect(function () { return fn; }, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
@@ -151,2 +230,5 @@ var _a = __read(react.useState(0), 2), setTick = _a[1]; | ||
var EMPTY_OBJECT = {}; | ||
function observerComponentNameFor(baseComponentName) { | ||
return "observer" + baseComponentName; | ||
} | ||
function useObserver(fn, baseComponentName, options) { | ||
@@ -160,12 +242,60 @@ if (baseComponentName === void 0) { baseComponentName = "observed"; } | ||
var forceUpdate = wantedForceUpdateHook(); | ||
var reaction = react.useRef(null); | ||
if (!reaction.current) { | ||
reaction.current = new mobx.Reaction("observer(" + baseComponentName + ")", function () { | ||
forceUpdate(); | ||
// StrictMode/ConcurrentMode/Suspense may mean that our component is | ||
// rendered and abandoned multiple times, so we need to track leaked | ||
// Reactions. | ||
var reactionTrackingRef = react.useRef(null); | ||
if (!reactionTrackingRef.current) { | ||
// First render for this component (or first time since a previous | ||
// reaction from an abandoned render was disposed). | ||
var newReaction_1 = new mobx.Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// Observable has changed, meaning we want to re-render | ||
// BUT if we're a component that hasn't yet got to the useEffect() | ||
// stage, we might be a component that _started_ to render, but | ||
// got dropped, and we don't want to make state changes then. | ||
// (It triggers warnings in StrictMode, for a start.) | ||
if (trackingData_1.mounted) { | ||
// We have reached useEffect(), so we're mounted, and can trigger an update | ||
forceUpdate(); | ||
} | ||
else { | ||
// We haven't yet reached useEffect(), so we'll need to trigger a re-render | ||
// when (and if) useEffect() arrives. The easiest way to do that is just to | ||
// drop our current reaction and allow useEffect() to recreate it. | ||
newReaction_1.dispose(); | ||
reactionTrackingRef.current = null; | ||
} | ||
}); | ||
var trackingData_1 = createTrackingData(newReaction_1); | ||
reactionTrackingRef.current = trackingData_1; | ||
scheduleCleanupOfReactionIfLeaked(reactionTrackingRef); | ||
} | ||
var reaction = reactionTrackingRef.current.reaction; | ||
react.useDebugValue(reaction, printDebugValue); | ||
useUnmount(function () { | ||
reaction.current.dispose(); | ||
}); | ||
react.useEffect(function () { | ||
// Called on first mount only | ||
recordReactionAsCommitted(reactionTrackingRef); | ||
if (reactionTrackingRef.current) { | ||
// Great. We've already got our reaction from our render; | ||
// all we need to do is to record that it's now mounted, | ||
// to allow future observable changes to trigger re-renders | ||
reactionTrackingRef.current.mounted = true; | ||
} | ||
else { | ||
// The reaction we set up in our render has been disposed. | ||
// This is either due to bad timings of renderings, e.g. our | ||
// component was paused for a _very_ long time, and our | ||
// reaction got cleaned up, or we got a observable change | ||
// between render and useEffect | ||
// Re-create the reaction | ||
reactionTrackingRef.current = { | ||
reaction: new mobx.Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// We've definitely already been mounted at this point | ||
forceUpdate(); | ||
}), | ||
cleanAt: Infinity | ||
}; | ||
forceUpdate(); | ||
} | ||
return function () { return reactionTrackingRef.current.reaction.dispose(); }; | ||
}, []); | ||
// render the original component, but have the | ||
@@ -176,3 +306,3 @@ // reaction track the observables, so that rendering | ||
var exception; | ||
reaction.current.track(function () { | ||
reaction.track(function () { | ||
try { | ||
@@ -186,3 +316,2 @@ rendering = fn(); | ||
if (exception) { | ||
reaction.current.dispose(); | ||
throw exception; // re-throw any exceptions catched during rendering | ||
@@ -195,3 +324,3 @@ } | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
// 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()) { | ||
@@ -272,11 +401,11 @@ return baseComponent; | ||
exports.useObservable = useObservable; | ||
exports.Observer = ObserverComponent; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.observer = observer; | ||
exports.useComputed = useComputed; | ||
exports.useDisposable = useDisposable; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.useForceUpdate = useForceUpdate; | ||
exports.useObservable = useObservable; | ||
exports.useObserver = useObserver; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.observer = observer; | ||
exports.useObserver = useObserver; | ||
exports.Observer = ObserverComponent; | ||
exports.useForceUpdate = useForceUpdate; | ||
@@ -283,0 +412,0 @@ Object.defineProperty(exports, '__esModule', { value: true }); |
@@ -1,1 +0,1 @@ | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("mobx"),require("react")):"function"==typeof define&&define.amd?define(["exports","mobx","react"],r):r((e=e||self).mobxReact={},e.mobx,e.React)}(this,function(e,r,n){"use strict";if(!n.useState)throw new Error("mobx-react-lite requires React with Hooks support");if(!r.spy)throw new Error("mobx-react-lite requires mobx at least version 4 to be available");var t=function(){};var o=!1;function u(){return o}var i=function(){return(i=Object.assign||function(e){for(var r,n=1,t=arguments.length;n<t;n++)for(var o in r=arguments[n])Object.prototype.hasOwnProperty.call(r,o)&&(e[o]=r[o]);return e}).apply(this,arguments)};function c(e){return e.current?r.getDependencyTree(e.current):"<unknown>"}var f=[];function a(){var e=function(e,r){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var t,o,u=n.call(e),i=[];try{for(;(void 0===r||r-- >0)&&!(t=u.next()).done;)i.push(t.value)}catch(e){o={error:e}}finally{try{t&&!t.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}return i}(n.useState(0),2)[1];return n.useCallback(function(){e(function(e){return e+1})},[])}var s={};function l(e,t,o){if(void 0===t&&(t="observed"),void 0===o&&(o=s),u())return e();var i,l,p=(o.useForceUpdate||a)(),d=n.useRef(null);if(d.current||(d.current=new r.Reaction("observer("+t+")",function(){p()})),n.useDebugValue(d,c),function(e){n.useEffect(function(){return e},f)}(function(){d.current.dispose()}),d.current.track(function(){try{i=e()}catch(e){l=e}}),l)throw d.current.dispose(),l;return i}var p={$$typeof:!0,render:!0,compare:!0,type:!0};function d(e){var r=e.children,n=e.render,t=r||n;return"function"!=typeof t?null:l(t)}function v(e,r,n,t,o){var u="children"===r?"render":"children",i="function"==typeof e[r],c="function"==typeof e[u];return i&&c?new Error("MobX Observer: Do not use children and render in the same time in`"+n):i||c?null:new Error("Invalid prop `"+o+"` of type `"+typeof e[r]+"` supplied to `"+n+"`, expected `function`.")}d.propTypes={children:v,render:v},d.displayName="Observer",e.useObservable=function(e){var t=n.useRef(null);return t.current||(t.current=r.observable(e)),t.current},e.useComputed=function(e,t){return void 0===t&&(t=[]),n.useMemo(function(){return r.computed(e)},t).get()},e.useDisposable=function(e,r){void 0===r&&(r=[]);var o=n.useRef(null),u=n.useRef(!1);function i(r){if(u.current)return t;if(!o.current){var n=e();if("function"!=typeof n){var i=new Error("generated disposer must be a function");return console.error(i),t}o.current=n}return function(){o.current&&(o.current(),o.current=null),r&&(u.current=!0)}}return n.useEffect(function(){return i(!1)},r),i(!0)},e.isUsingStaticRendering=u,e.useStaticRendering=function(e){o=e},e.observer=function(e,r){if(u())return e;var t,o,c,f=i({forwardRef:!1},r),a=e.displayName||e.name,s=function(r,n){return l(function(){return e(r,n)},a)};return s.displayName=a,t=f.forwardRef?n.memo(n.forwardRef(s)):n.memo(s),o=e,c=t,Object.keys(o).forEach(function(e){o.hasOwnProperty(e)&&!p[e]&&Object.defineProperty(c,e,Object.getOwnPropertyDescriptor(o,e))}),t.displayName=a,t},e.useObserver=l,e.Observer=d,e.useForceUpdate=a,Object.defineProperty(e,"__esModule",{value:!0})}); | ||
!function(e,r){"object"==typeof exports&&"undefined"!=typeof module?r(exports,require("mobx"),require("react")):"function"==typeof define&&define.amd?define(["exports","mobx","react"],r):r((e=e||self).mobxReact={},e.mobx,e.React)}(this,function(e,r,n){"use strict";if(!n.useState)throw new Error("mobx-react-lite requires React with Hooks support");if(!r.spy)throw new Error("mobx-react-lite requires mobx at least version 4 to be available");var t=function(){};var o=!1;function u(){return o}var i=function(){return(i=Object.assign||function(e){for(var r,n=1,t=arguments.length;n<t;n++)for(var o in r=arguments[n])Object.prototype.hasOwnProperty.call(r,o)&&(e[o]=r[o]);return e}).apply(this,arguments)};function c(e){return r.getDependencyTree(e)}var a,f=1e4,l=1e4,s=new Set;function d(){void 0===a&&(a=setTimeout(p,l))}function p(){var e,r;a=void 0;var n=Date.now();try{for(var t=function(e){var r="function"==typeof Symbol&&e[Symbol.iterator],n=0;return r?r.call(e):{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}}}(s),o=t.next();!o.done;o=t.next()){var u=o.value,i=u.current;i&&n>=i.cleanAt&&(i.reaction.dispose(),u.current=null,s.delete(u))}}catch(r){e={error:r}}finally{try{o&&!o.done&&(r=t.return)&&r.call(t)}finally{if(e)throw e.error}}s.size>0&&d()}function v(){var e=function(e,r){var n="function"==typeof Symbol&&e[Symbol.iterator];if(!n)return e;var t,o,u=n.call(e),i=[];try{for(;(void 0===r||r-- >0)&&!(t=u.next()).done;)i.push(t.value)}catch(e){o={error:e}}finally{try{t&&!t.done&&(n=u.return)&&n.call(u)}finally{if(o)throw o.error}}return i}(n.useState(0),2)[1];return n.useCallback(function(){e(function(e){return e+1})},[])}var y={};function b(e){return"observer"+e}function m(e,t,o){if(void 0===t&&(t="observed"),void 0===o&&(o=y),u())return e();var i,a=(o.useForceUpdate||v)(),l=n.useRef(null);if(!l.current){var p=new r.Reaction(b(t),function(){m.mounted?a():(p.dispose(),l.current=null)}),m=function(e){return{cleanAt:Date.now()+f,reaction:e}}(p);l.current=m,i=l,s.add(i),d()}var h,w,x=l.current.reaction;if(n.useDebugValue(x,c),n.useEffect(function(){var e;return e=l,s.delete(e),l.current?l.current.mounted=!0:(l.current={reaction:new r.Reaction(b(t),function(){a()}),cleanAt:1/0},a()),function(){return l.current.reaction.dispose()}},[]),x.track(function(){try{h=e()}catch(e){w=e}}),w)throw w;return h}var h={$$typeof:!0,render:!0,compare:!0,type:!0};function w(e){var r=e.children,n=e.render,t=r||n;return"function"!=typeof t?null:m(t)}function x(e,r,n,t,o){var u="children"===r?"render":"children",i="function"==typeof e[r],c="function"==typeof e[u];return i&&c?new Error("MobX Observer: Do not use children and render in the same time in`"+n):i||c?null:new Error("Invalid prop `"+o+"` of type `"+typeof e[r]+"` supplied to `"+n+"`, expected `function`.")}w.propTypes={children:x,render:x},w.displayName="Observer",e.Observer=w,e.isUsingStaticRendering=u,e.observer=function(e,r){if(u())return e;var t,o,c,a=i({forwardRef:!1},r),f=e.displayName||e.name,l=function(r,n){return m(function(){return e(r,n)},f)};return l.displayName=f,t=a.forwardRef?n.memo(n.forwardRef(l)):n.memo(l),o=e,c=t,Object.keys(o).forEach(function(e){o.hasOwnProperty(e)&&!h[e]&&Object.defineProperty(c,e,Object.getOwnPropertyDescriptor(o,e))}),t.displayName=f,t},e.useComputed=function(e,t){return void 0===t&&(t=[]),n.useMemo(function(){return r.computed(e)},t).get()},e.useDisposable=function(e,r){void 0===r&&(r=[]);var o=n.useRef(null),u=n.useRef(!1);function i(r){if(u.current)return t;if(!o.current){var n=e();if("function"!=typeof n){var i=new Error("generated disposer must be a function");return console.error(i),t}o.current=n}return function(){o.current&&(o.current(),o.current=null),r&&(u.current=!0)}}return n.useEffect(function(){return i(!1)},r),i(!0)},e.useForceUpdate=v,e.useObservable=function(e){var t=n.useRef(null);return t.current||(t.current=r.observable(e)),t.current},e.useObserver=m,e.useStaticRendering=function(e){o=e},Object.defineProperty(e,"__esModule",{value:!0})}); |
@@ -109,2 +109,13 @@ import { spy, observable, computed, getDependencyTree, Reaction } from 'mobx'; | ||
function __values(o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
} | ||
function __read(o, n) { | ||
@@ -128,12 +139,80 @@ var m = typeof Symbol === "function" && o[Symbol.iterator]; | ||
function printDebugValue(v) { | ||
if (!v.current) { | ||
return "<unknown>"; | ||
return getDependencyTree(v); | ||
} | ||
function createTrackingData(reaction) { | ||
var trackingData = { | ||
cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS, | ||
reaction: reaction | ||
}; | ||
return trackingData; | ||
} | ||
/** | ||
* The minimum time before we'll clean up a Reaction created in a render | ||
* for a component that hasn't managed to run its effects. This needs to | ||
* be big enough to ensure that a component won't turn up and have its | ||
* effects run without being re-rendered. | ||
*/ | ||
var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000; | ||
/** | ||
* The frequency with which we'll check for leaked reactions. | ||
*/ | ||
var CLEANUP_TIMER_LOOP_MILLIS = 10000; | ||
/** | ||
* Reactions created by components that have yet to be fully mounted. | ||
*/ | ||
var uncommittedReactionRefs = new Set(); | ||
/** | ||
* Latest 'uncommitted reactions' cleanup timer handle. | ||
*/ | ||
var reactionCleanupHandle; | ||
function ensureCleanupTimerRunning() { | ||
if (reactionCleanupHandle === undefined) { | ||
reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS); | ||
} | ||
return getDependencyTree(v.current); | ||
} | ||
function scheduleCleanupOfReactionIfLeaked(ref) { | ||
uncommittedReactionRefs.add(ref); | ||
ensureCleanupTimerRunning(); | ||
} | ||
function recordReactionAsCommitted(reactionRef) { | ||
uncommittedReactionRefs.delete(reactionRef); | ||
} | ||
/** | ||
* Run by the cleanup timer to dispose any outstanding reactions | ||
*/ | ||
function cleanUncommittedReactions() { | ||
var e_1, _a; | ||
reactionCleanupHandle = undefined; | ||
// Loop through all the candidate leaked reactions; those older | ||
// than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied. | ||
var now = Date.now(); | ||
try { | ||
for (var uncommittedReactionRefs_1 = __values(uncommittedReactionRefs), uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next(); !uncommittedReactionRefs_1_1.done; uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next()) { | ||
var ref = uncommittedReactionRefs_1_1.value; | ||
var tracking = ref.current; | ||
if (tracking) { | ||
if (now >= tracking.cleanAt) { | ||
// It's time to tidy up this leaked reaction. | ||
tracking.reaction.dispose(); | ||
ref.current = null; | ||
uncommittedReactionRefs.delete(ref); | ||
} | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (uncommittedReactionRefs_1_1 && !uncommittedReactionRefs_1_1.done && (_a = uncommittedReactionRefs_1.return)) _a.call(uncommittedReactionRefs_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
if (uncommittedReactionRefs.size > 0) { | ||
// We've just finished a round of cleanups but there are still | ||
// some leak candidates outstanding. | ||
ensureCleanupTimerRunning(); | ||
} | ||
} | ||
var EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
useEffect(function () { return fn; }, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
@@ -148,2 +227,5 @@ var _a = __read(useState(0), 2), setTick = _a[1]; | ||
var EMPTY_OBJECT = {}; | ||
function observerComponentNameFor(baseComponentName) { | ||
return "observer" + baseComponentName; | ||
} | ||
function useObserver(fn, baseComponentName, options) { | ||
@@ -157,12 +239,60 @@ if (baseComponentName === void 0) { baseComponentName = "observed"; } | ||
var forceUpdate = wantedForceUpdateHook(); | ||
var reaction = useRef(null); | ||
if (!reaction.current) { | ||
reaction.current = new Reaction("observer(" + baseComponentName + ")", function () { | ||
forceUpdate(); | ||
// StrictMode/ConcurrentMode/Suspense may mean that our component is | ||
// rendered and abandoned multiple times, so we need to track leaked | ||
// Reactions. | ||
var reactionTrackingRef = useRef(null); | ||
if (!reactionTrackingRef.current) { | ||
// First render for this component (or first time since a previous | ||
// reaction from an abandoned render was disposed). | ||
var newReaction_1 = new Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// Observable has changed, meaning we want to re-render | ||
// BUT if we're a component that hasn't yet got to the useEffect() | ||
// stage, we might be a component that _started_ to render, but | ||
// got dropped, and we don't want to make state changes then. | ||
// (It triggers warnings in StrictMode, for a start.) | ||
if (trackingData_1.mounted) { | ||
// We have reached useEffect(), so we're mounted, and can trigger an update | ||
forceUpdate(); | ||
} | ||
else { | ||
// We haven't yet reached useEffect(), so we'll need to trigger a re-render | ||
// when (and if) useEffect() arrives. The easiest way to do that is just to | ||
// drop our current reaction and allow useEffect() to recreate it. | ||
newReaction_1.dispose(); | ||
reactionTrackingRef.current = null; | ||
} | ||
}); | ||
var trackingData_1 = createTrackingData(newReaction_1); | ||
reactionTrackingRef.current = trackingData_1; | ||
scheduleCleanupOfReactionIfLeaked(reactionTrackingRef); | ||
} | ||
var reaction = reactionTrackingRef.current.reaction; | ||
useDebugValue(reaction, printDebugValue); | ||
useUnmount(function () { | ||
reaction.current.dispose(); | ||
}); | ||
useEffect(function () { | ||
// Called on first mount only | ||
recordReactionAsCommitted(reactionTrackingRef); | ||
if (reactionTrackingRef.current) { | ||
// Great. We've already got our reaction from our render; | ||
// all we need to do is to record that it's now mounted, | ||
// to allow future observable changes to trigger re-renders | ||
reactionTrackingRef.current.mounted = true; | ||
} | ||
else { | ||
// The reaction we set up in our render has been disposed. | ||
// This is either due to bad timings of renderings, e.g. our | ||
// component was paused for a _very_ long time, and our | ||
// reaction got cleaned up, or we got a observable change | ||
// between render and useEffect | ||
// Re-create the reaction | ||
reactionTrackingRef.current = { | ||
reaction: new Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// We've definitely already been mounted at this point | ||
forceUpdate(); | ||
}), | ||
cleanAt: Infinity | ||
}; | ||
forceUpdate(); | ||
} | ||
return function () { return reactionTrackingRef.current.reaction.dispose(); }; | ||
}, []); | ||
// render the original component, but have the | ||
@@ -173,3 +303,3 @@ // reaction track the observables, so that rendering | ||
var exception; | ||
reaction.current.track(function () { | ||
reaction.track(function () { | ||
try { | ||
@@ -183,3 +313,2 @@ rendering = fn(); | ||
if (exception) { | ||
reaction.current.dispose(); | ||
throw exception; // re-throw any exceptions catched during rendering | ||
@@ -192,3 +321,3 @@ } | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
// 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()) { | ||
@@ -269,2 +398,2 @@ return baseComponent; | ||
export { useObservable, useComputed, useDisposable, isUsingStaticRendering, useStaticRendering, observer, useObserver, ObserverComponent as Observer, useForceUpdate }; | ||
export { ObserverComponent as Observer, isUsingStaticRendering, observer, useComputed, useDisposable, useForceUpdate, useObservable, useObserver, useStaticRendering }; |
@@ -113,2 +113,13 @@ 'use strict'; | ||
function __values(o) { | ||
var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; | ||
if (m) return m.call(o); | ||
return { | ||
next: function () { | ||
if (o && i >= o.length) o = void 0; | ||
return { value: o && o[i++], done: !o }; | ||
} | ||
}; | ||
} | ||
function __read(o, n) { | ||
@@ -132,12 +143,80 @@ var m = typeof Symbol === "function" && o[Symbol.iterator]; | ||
function printDebugValue(v) { | ||
if (!v.current) { | ||
return "<unknown>"; | ||
return mobx.getDependencyTree(v); | ||
} | ||
function createTrackingData(reaction) { | ||
var trackingData = { | ||
cleanAt: Date.now() + CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS, | ||
reaction: reaction | ||
}; | ||
return trackingData; | ||
} | ||
/** | ||
* The minimum time before we'll clean up a Reaction created in a render | ||
* for a component that hasn't managed to run its effects. This needs to | ||
* be big enough to ensure that a component won't turn up and have its | ||
* effects run without being re-rendered. | ||
*/ | ||
var CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS = 10000; | ||
/** | ||
* The frequency with which we'll check for leaked reactions. | ||
*/ | ||
var CLEANUP_TIMER_LOOP_MILLIS = 10000; | ||
/** | ||
* Reactions created by components that have yet to be fully mounted. | ||
*/ | ||
var uncommittedReactionRefs = new Set(); | ||
/** | ||
* Latest 'uncommitted reactions' cleanup timer handle. | ||
*/ | ||
var reactionCleanupHandle; | ||
function ensureCleanupTimerRunning() { | ||
if (reactionCleanupHandle === undefined) { | ||
reactionCleanupHandle = setTimeout(cleanUncommittedReactions, CLEANUP_TIMER_LOOP_MILLIS); | ||
} | ||
return mobx.getDependencyTree(v.current); | ||
} | ||
function scheduleCleanupOfReactionIfLeaked(ref) { | ||
uncommittedReactionRefs.add(ref); | ||
ensureCleanupTimerRunning(); | ||
} | ||
function recordReactionAsCommitted(reactionRef) { | ||
uncommittedReactionRefs.delete(reactionRef); | ||
} | ||
/** | ||
* Run by the cleanup timer to dispose any outstanding reactions | ||
*/ | ||
function cleanUncommittedReactions() { | ||
var e_1, _a; | ||
reactionCleanupHandle = undefined; | ||
// Loop through all the candidate leaked reactions; those older | ||
// than CLEANUP_LEAKED_REACTIONS_AFTER_MILLIS get tidied. | ||
var now = Date.now(); | ||
try { | ||
for (var uncommittedReactionRefs_1 = __values(uncommittedReactionRefs), uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next(); !uncommittedReactionRefs_1_1.done; uncommittedReactionRefs_1_1 = uncommittedReactionRefs_1.next()) { | ||
var ref = uncommittedReactionRefs_1_1.value; | ||
var tracking = ref.current; | ||
if (tracking) { | ||
if (now >= tracking.cleanAt) { | ||
// It's time to tidy up this leaked reaction. | ||
tracking.reaction.dispose(); | ||
ref.current = null; | ||
uncommittedReactionRefs.delete(ref); | ||
} | ||
} | ||
} | ||
} | ||
catch (e_1_1) { e_1 = { error: e_1_1 }; } | ||
finally { | ||
try { | ||
if (uncommittedReactionRefs_1_1 && !uncommittedReactionRefs_1_1.done && (_a = uncommittedReactionRefs_1.return)) _a.call(uncommittedReactionRefs_1); | ||
} | ||
finally { if (e_1) throw e_1.error; } | ||
} | ||
if (uncommittedReactionRefs.size > 0) { | ||
// We've just finished a round of cleanups but there are still | ||
// some leak candidates outstanding. | ||
ensureCleanupTimerRunning(); | ||
} | ||
} | ||
var EMPTY_ARRAY = []; | ||
function useUnmount(fn) { | ||
react.useEffect(function () { return fn; }, EMPTY_ARRAY); | ||
} | ||
function useForceUpdate() { | ||
@@ -152,2 +231,5 @@ var _a = __read(react.useState(0), 2), setTick = _a[1]; | ||
var EMPTY_OBJECT = {}; | ||
function observerComponentNameFor(baseComponentName) { | ||
return "observer" + baseComponentName; | ||
} | ||
function useObserver(fn, baseComponentName, options) { | ||
@@ -161,12 +243,60 @@ if (baseComponentName === void 0) { baseComponentName = "observed"; } | ||
var forceUpdate = wantedForceUpdateHook(); | ||
var reaction = react.useRef(null); | ||
if (!reaction.current) { | ||
reaction.current = new mobx.Reaction("observer(" + baseComponentName + ")", function () { | ||
forceUpdate(); | ||
// StrictMode/ConcurrentMode/Suspense may mean that our component is | ||
// rendered and abandoned multiple times, so we need to track leaked | ||
// Reactions. | ||
var reactionTrackingRef = react.useRef(null); | ||
if (!reactionTrackingRef.current) { | ||
// First render for this component (or first time since a previous | ||
// reaction from an abandoned render was disposed). | ||
var newReaction_1 = new mobx.Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// Observable has changed, meaning we want to re-render | ||
// BUT if we're a component that hasn't yet got to the useEffect() | ||
// stage, we might be a component that _started_ to render, but | ||
// got dropped, and we don't want to make state changes then. | ||
// (It triggers warnings in StrictMode, for a start.) | ||
if (trackingData_1.mounted) { | ||
// We have reached useEffect(), so we're mounted, and can trigger an update | ||
forceUpdate(); | ||
} | ||
else { | ||
// We haven't yet reached useEffect(), so we'll need to trigger a re-render | ||
// when (and if) useEffect() arrives. The easiest way to do that is just to | ||
// drop our current reaction and allow useEffect() to recreate it. | ||
newReaction_1.dispose(); | ||
reactionTrackingRef.current = null; | ||
} | ||
}); | ||
var trackingData_1 = createTrackingData(newReaction_1); | ||
reactionTrackingRef.current = trackingData_1; | ||
scheduleCleanupOfReactionIfLeaked(reactionTrackingRef); | ||
} | ||
var reaction = reactionTrackingRef.current.reaction; | ||
react.useDebugValue(reaction, printDebugValue); | ||
useUnmount(function () { | ||
reaction.current.dispose(); | ||
}); | ||
react.useEffect(function () { | ||
// Called on first mount only | ||
recordReactionAsCommitted(reactionTrackingRef); | ||
if (reactionTrackingRef.current) { | ||
// Great. We've already got our reaction from our render; | ||
// all we need to do is to record that it's now mounted, | ||
// to allow future observable changes to trigger re-renders | ||
reactionTrackingRef.current.mounted = true; | ||
} | ||
else { | ||
// The reaction we set up in our render has been disposed. | ||
// This is either due to bad timings of renderings, e.g. our | ||
// component was paused for a _very_ long time, and our | ||
// reaction got cleaned up, or we got a observable change | ||
// between render and useEffect | ||
// Re-create the reaction | ||
reactionTrackingRef.current = { | ||
reaction: new mobx.Reaction(observerComponentNameFor(baseComponentName), function () { | ||
// We've definitely already been mounted at this point | ||
forceUpdate(); | ||
}), | ||
cleanAt: Infinity | ||
}; | ||
forceUpdate(); | ||
} | ||
return function () { return reactionTrackingRef.current.reaction.dispose(); }; | ||
}, []); | ||
// render the original component, but have the | ||
@@ -177,3 +307,3 @@ // reaction track the observables, so that rendering | ||
var exception; | ||
reaction.current.track(function () { | ||
reaction.track(function () { | ||
try { | ||
@@ -187,3 +317,2 @@ rendering = fn(); | ||
if (exception) { | ||
reaction.current.dispose(); | ||
throw exception; // re-throw any exceptions catched during rendering | ||
@@ -196,3 +325,3 @@ } | ||
function observer(baseComponent, options) { | ||
// The working of observer is explaind step by step in this talk: https://www.youtube.com/watch?v=cPF4iBedoF0&feature=youtu.be&t=1307 | ||
// 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()) { | ||
@@ -273,10 +402,10 @@ return baseComponent; | ||
exports.useObservable = useObservable; | ||
exports.Observer = ObserverComponent; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.observer = observer; | ||
exports.useComputed = useComputed; | ||
exports.useDisposable = useDisposable; | ||
exports.isUsingStaticRendering = isUsingStaticRendering; | ||
exports.useForceUpdate = useForceUpdate; | ||
exports.useObservable = useObservable; | ||
exports.useObserver = useObserver; | ||
exports.useStaticRendering = useStaticRendering; | ||
exports.observer = observer; | ||
exports.useObserver = useObserver; | ||
exports.Observer = ObserverComponent; | ||
exports.useForceUpdate = useForceUpdate; |
@@ -8,2 +8,2 @@ /// <reference types="react" /> | ||
}): React.MemoExoticComponent<React.ForwardRefExoticComponent<React.PropsWithoutRef<P> & React.RefAttributes<TRef>>>; | ||
export declare function observer<P extends object>(baseComponent: React.FunctionComponent<P>, options?: IObserverOptions): React.NamedExoticComponent<P>; | ||
export declare function observer<P extends object>(baseComponent: React.FunctionComponent<P>, options?: IObserverOptions): React.FunctionComponent<P>; |
@@ -1,3 +0,2 @@ | ||
/// <reference types="react" /> | ||
import { Reaction } from "mobx"; | ||
export declare function printDebugValue(v: React.MutableRefObject<Reaction | null>): "<unknown>" | import("mobx").IDependencyTree; | ||
export declare function printDebugValue(v: Reaction): import("mobx").IDependencyTree; |
@@ -1,2 +0,1 @@ | ||
export declare function useUnmount(fn: () => void): void; | ||
export declare function useForceUpdate(): () => void; |
{ | ||
"name": "mobx-react-lite", | ||
"version": "1.2.0", | ||
"version": "2.0.0-alpha.0", | ||
"description": "Lightweight React bindings for MobX based on React 16.8 and Hooks", | ||
@@ -37,31 +37,31 @@ "main": "dist/index.js", | ||
"@types/jest": "24.0.11", | ||
"@types/node": "11.11.8", | ||
"@types/node": "11.13.8", | ||
"@types/react": "16.8.8", | ||
"@types/react-dom": "16.8.3", | ||
"@types/react-dom": "16.8.4", | ||
"coveralls": "3.0.3", | ||
"husky": "1.3.1", | ||
"jest": "24.5.0", | ||
"husky": "2.1.0", | ||
"jest": "24.7.1", | ||
"jest-dom": "3.1.3", | ||
"jest-environment-jsdom": "24.5.0", | ||
"jest-environment-jsdom": "24.7.1", | ||
"jest-mock-console": "0.4.2", | ||
"lint-staged": "8.1.5", | ||
"lodash": "4.17.11", | ||
"mobx": "5.9.0", | ||
"prettier": "1.16.4", | ||
"react": "16.8.5", | ||
"react-dom": "16.8.5", | ||
"react-testing-library": "6.0.2", | ||
"mobx": "5.9.4", | ||
"prettier": "1.17.0", | ||
"react": "16.8.6", | ||
"react-dom": "16.8.6", | ||
"react-testing-library": "7.0.0", | ||
"rimraf": "2.6.3", | ||
"rollup": "1.7.3", | ||
"rollup": "1.10.1", | ||
"rollup-plugin-alias": "1.5.1", | ||
"rollup-plugin-commonjs": "9.2.2", | ||
"rollup-plugin-commonjs": "9.3.4", | ||
"rollup-plugin-filesize": "6.0.1", | ||
"rollup-plugin-node-resolve": "4.0.1", | ||
"rollup-plugin-replace": "2.1.1", | ||
"rollup-plugin-node-resolve": "4.2.3", | ||
"rollup-plugin-replace": "2.2.0", | ||
"rollup-plugin-terser": "4.0.4", | ||
"rollup-plugin-typescript2": "0.20.1", | ||
"ts-jest": "24.0.0", | ||
"tslint": "5.14.0", | ||
"rollup-plugin-typescript2": "0.21.0", | ||
"ts-jest": "24.0.2", | ||
"tslint": "5.16.0", | ||
"tslint-config-prettier": "1.18.0", | ||
"typescript": "3.3.4000" | ||
"typescript": "3.4.5" | ||
}, | ||
@@ -68,0 +68,0 @@ "keywords": [ |
@@ -27,3 +27,2 @@ # mobx-react-lite <!-- omit in toc --> | ||
- [Why no Provider/inject?](#why-no-providerinject) | ||
- [What about smart/dumb components?](#what-about-smartdumb-components) | ||
@@ -34,3 +33,3 @@ ## API documentation | ||
`Observer` is a React component, which applies `observer` to an anonymous region in your component. | ||
`Observer` is a React component, which applies observer to an anonymous region in your component. | ||
It takes as children a single, argumentless function which should return exactly one React component. | ||
@@ -309,50 +308,1 @@ The rendering in the function will be tracked and automatically re-rendered when needed. | ||
``` | ||
## What about smart/dumb components? | ||
The React hooks don't force anyone to suddenly have a state inside a _dumb component_ that is supposed to only render stuff. You can separate your concerns in a similar fashion. | ||
```tsx | ||
import { createSelector } from "react-selector-hooks" | ||
const userSelector = createSelector(({ user }) => ({ | ||
name: user.name, | ||
age: user.age | ||
})) | ||
function UiComponent({ name, age }) { | ||
return ( | ||
<div> | ||
<div>Name: {name}</div> | ||
<div>Age: {age}</div> | ||
</div> | ||
) | ||
} | ||
export default () => { | ||
// you may extract these two lines into a custom hook | ||
const store = useContext(StoreContext) | ||
const data = userSelector(store) | ||
return UiComponent({ ...data }) | ||
// perhaps wrap it inside observer in here? | ||
return observer(UiComponent({ ...data })) | ||
} | ||
``` | ||
It may look a bit more verbose than a _classic_ inject, but there is nothing stopping you to make your own `inject` HOC which is so much easier since everything is just a function. | ||
```tsx | ||
// make universal HOC | ||
const inject = (useSelector, baseComponent) => | ||
React.useMemo(props => { | ||
const store = useContext(StoreContext) | ||
const selected = useSelector(store) | ||
return baseComponent({ ...selected, ...props }) | ||
}) | ||
// use the HOC with a selector | ||
export default inject(userSelector, UiComponent) | ||
``` |
{ | ||
"extends": [ | ||
"config:base" | ||
] | ||
], | ||
"automerge": true, | ||
"automergeType": "branch", | ||
"major": { | ||
"automerge": false | ||
} | ||
} |
Sorry, the diff of this file is not supported yet
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
314232
23
2030
2
306