mobx-react 7.6.0 → 9.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/README.md +96 -15
- package/dist/disposeOnUnmount.d.ts +6 -0
- package/dist/mobxreact.cjs.development.js +199 -339
- package/dist/mobxreact.cjs.development.js.map +1 -1
- package/dist/mobxreact.cjs.production.min.js +1 -1
- package/dist/mobxreact.cjs.production.min.js.map +1 -1
- package/dist/mobxreact.esm.development.js +201 -341
- package/dist/mobxreact.esm.development.js.map +1 -1
- package/dist/mobxreact.esm.js +201 -341
- package/dist/mobxreact.esm.js.map +1 -1
- package/dist/mobxreact.esm.production.min.js +1 -1
- package/dist/mobxreact.esm.production.min.js.map +1 -1
- package/dist/mobxreact.umd.development.js +199 -339
- package/dist/mobxreact.umd.development.js.map +1 -1
- package/dist/mobxreact.umd.production.min.js +1 -1
- package/dist/mobxreact.umd.production.min.js.map +1 -1
- package/dist/observerClass.d.ts +2 -2
- package/dist/utils/utils.d.ts +0 -1
- package/package.json +6 -5
- package/src/disposeOnUnmount.ts +29 -3
- package/src/index.ts +7 -2
- package/src/observerClass.ts +179 -161
- package/src/utils/utils.ts +3 -20
package/dist/observerClass.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
export declare function makeClassComponentObserver(componentClass:
|
|
1
|
+
import { ComponentClass } from "react";
|
|
2
|
+
export declare function makeClassComponentObserver(componentClass: ComponentClass<any, any>): ComponentClass<any, any>;
|
package/dist/utils/utils.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mobx-react",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "9.0.0",
|
|
4
4
|
"description": "React bindings for MobX. Create fully reactive components.",
|
|
5
5
|
"source": "src/index.ts",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://mobx.js.org",
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"mobx-react-lite": "^
|
|
39
|
+
"mobx-react-lite": "^4.0.3"
|
|
40
40
|
},
|
|
41
41
|
"peerDependencies": {
|
|
42
|
-
"mobx": "^6.
|
|
42
|
+
"mobx": "^6.9.0",
|
|
43
43
|
"react": "^16.8.0 || ^17 || ^18"
|
|
44
44
|
},
|
|
45
45
|
"peerDependenciesMeta": {
|
|
@@ -51,8 +51,9 @@
|
|
|
51
51
|
}
|
|
52
52
|
},
|
|
53
53
|
"devDependencies": {
|
|
54
|
-
"mobx": "^6.
|
|
55
|
-
"mobx-react-lite": "^
|
|
54
|
+
"mobx": "^6.9.0",
|
|
55
|
+
"mobx-react-lite": "^4.0.3",
|
|
56
|
+
"expose-gc": "^1.0.0"
|
|
56
57
|
},
|
|
57
58
|
"keywords": [
|
|
58
59
|
"mobx",
|
package/src/disposeOnUnmount.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React from "react"
|
|
2
|
-
import { patch
|
|
2
|
+
import { patch } from "./utils/utils"
|
|
3
|
+
|
|
4
|
+
const reactMajorVersion = Number.parseInt(React.version.split(".")[0])
|
|
5
|
+
let warnedAboutDisposeOnUnmountDeprecated = false
|
|
3
6
|
|
|
4
7
|
type Disposer = () => void
|
|
5
8
|
|
|
6
|
-
const protoStoreKey =
|
|
7
|
-
const instStoreKey =
|
|
9
|
+
const protoStoreKey = Symbol("disposeOnUnmountProto")
|
|
10
|
+
const instStoreKey = Symbol("disposeOnUnmountInst")
|
|
8
11
|
|
|
9
12
|
function runDisposersOnWillUnmount() {
|
|
10
13
|
;[...(this[protoStoreKey] || []), ...(this[instStoreKey] || [])].forEach(propKeyOrFunction => {
|
|
@@ -17,12 +20,22 @@ function runDisposersOnWillUnmount() {
|
|
|
17
20
|
})
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
/**
|
|
24
|
+
* @deprecated `disposeOnUnmount` is not compatible with React 18 and higher.
|
|
25
|
+
*/
|
|
20
26
|
export function disposeOnUnmount(target: React.Component<any, any>, propertyKey: PropertyKey): void
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @deprecated `disposeOnUnmount` is not compatible with React 18 and higher.
|
|
30
|
+
*/
|
|
21
31
|
export function disposeOnUnmount<TF extends Disposer | Array<Disposer>>(
|
|
22
32
|
target: React.Component<any, any>,
|
|
23
33
|
fn: TF
|
|
24
34
|
): TF
|
|
25
35
|
|
|
36
|
+
/**
|
|
37
|
+
* @deprecated `disposeOnUnmount` is not compatible with React 18 and higher.
|
|
38
|
+
*/
|
|
26
39
|
export function disposeOnUnmount(
|
|
27
40
|
target: React.Component<any, any>,
|
|
28
41
|
propertyKeyOrFunction: PropertyKey | Disposer | Array<Disposer>
|
|
@@ -31,6 +44,19 @@ export function disposeOnUnmount(
|
|
|
31
44
|
return propertyKeyOrFunction.map(fn => disposeOnUnmount(target, fn))
|
|
32
45
|
}
|
|
33
46
|
|
|
47
|
+
if (!warnedAboutDisposeOnUnmountDeprecated) {
|
|
48
|
+
if (reactMajorVersion >= 18) {
|
|
49
|
+
console.error(
|
|
50
|
+
"[mobx-react] disposeOnUnmount is not compatible with React 18 and higher. Don't use it."
|
|
51
|
+
)
|
|
52
|
+
} else {
|
|
53
|
+
console.warn(
|
|
54
|
+
"[mobx-react] disposeOnUnmount is deprecated. It won't work correctly with React 18 and higher."
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
warnedAboutDisposeOnUnmountDeprecated = true
|
|
58
|
+
}
|
|
59
|
+
|
|
34
60
|
const c = Object.getPrototypeOf(target).constructor
|
|
35
61
|
const c2 = Object.getPrototypeOf(target.constructor)
|
|
36
62
|
// Special case for react-hot-loader
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
import { observable } from "mobx"
|
|
2
2
|
import { Component } from "react"
|
|
3
3
|
|
|
4
|
-
if (!Component)
|
|
5
|
-
|
|
4
|
+
if (!Component) {
|
|
5
|
+
throw new Error("mobx-react requires React to be available")
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (!observable) {
|
|
9
|
+
throw new Error("mobx-react requires mobx to be available")
|
|
10
|
+
}
|
|
6
11
|
|
|
7
12
|
export {
|
|
8
13
|
Observer,
|
package/src/observerClass.ts
CHANGED
|
@@ -1,44 +1,76 @@
|
|
|
1
|
-
import { PureComponent, Component } from "react"
|
|
1
|
+
import { PureComponent, Component, ComponentClass, ClassAttributes } from "react"
|
|
2
2
|
import {
|
|
3
|
-
createAtom,
|
|
4
3
|
_allowStateChanges,
|
|
5
4
|
Reaction,
|
|
6
|
-
$mobx,
|
|
7
5
|
_allowStateReadsStart,
|
|
8
|
-
_allowStateReadsEnd
|
|
6
|
+
_allowStateReadsEnd,
|
|
7
|
+
_getGlobalState
|
|
9
8
|
} from "mobx"
|
|
10
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
isUsingStaticRendering,
|
|
11
|
+
_observerFinalizationRegistry as observerFinalizationRegistry
|
|
12
|
+
} from "mobx-react-lite"
|
|
13
|
+
import { shallowEqual, patch } from "./utils/utils"
|
|
11
14
|
|
|
12
|
-
|
|
15
|
+
const administrationSymbol = Symbol("ObserverAdministration")
|
|
16
|
+
const isMobXReactObserverSymbol = Symbol("isMobXReactObserver")
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
let observablePropDescriptors: PropertyDescriptorMap
|
|
19
|
+
if (__DEV__) {
|
|
20
|
+
observablePropDescriptors = {
|
|
21
|
+
props: createObservablePropDescriptor("props"),
|
|
22
|
+
state: createObservablePropDescriptor("state"),
|
|
23
|
+
context: createObservablePropDescriptor("context")
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
type ObserverAdministration = {
|
|
28
|
+
reaction: Reaction | null // also serves as disposed flag
|
|
29
|
+
forceUpdate: Function | null
|
|
30
|
+
mounted: boolean // we could use forceUpdate as mounted flag
|
|
31
|
+
name: string
|
|
32
|
+
// Used only on __DEV__
|
|
33
|
+
props: any
|
|
34
|
+
state: any
|
|
35
|
+
context: any
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function getAdministration(component: Component): ObserverAdministration {
|
|
39
|
+
// We create administration lazily, because we can't patch constructor
|
|
40
|
+
// and the exact moment of initialization partially depends on React internals.
|
|
41
|
+
// At the time of writing this, the first thing invoked is one of the observable getter/setter (state/props/context).
|
|
42
|
+
return (component[administrationSymbol] ??= {
|
|
43
|
+
reaction: null,
|
|
44
|
+
mounted: false,
|
|
45
|
+
forceUpdate: null,
|
|
46
|
+
name: getDisplayName(component.constructor as ComponentClass),
|
|
47
|
+
state: undefined,
|
|
48
|
+
props: undefined,
|
|
49
|
+
context: undefined
|
|
50
|
+
})
|
|
51
|
+
}
|
|
19
52
|
|
|
20
53
|
export function makeClassComponentObserver(
|
|
21
|
-
componentClass:
|
|
22
|
-
):
|
|
23
|
-
const
|
|
54
|
+
componentClass: ComponentClass<any, any>
|
|
55
|
+
): ComponentClass<any, any> {
|
|
56
|
+
const { prototype } = componentClass
|
|
24
57
|
|
|
25
|
-
if (componentClass[
|
|
26
|
-
const displayName = getDisplayName(
|
|
27
|
-
|
|
28
|
-
`The provided component class (${displayName})
|
|
29
|
-
has already been declared as an observer component.`
|
|
58
|
+
if (componentClass[isMobXReactObserverSymbol]) {
|
|
59
|
+
const displayName = getDisplayName(componentClass)
|
|
60
|
+
throw new Error(
|
|
61
|
+
`The provided component class (${displayName}) has already been declared as an observer component.`
|
|
30
62
|
)
|
|
31
63
|
} else {
|
|
32
|
-
componentClass[
|
|
64
|
+
componentClass[isMobXReactObserverSymbol] = true
|
|
33
65
|
}
|
|
34
66
|
|
|
35
|
-
if (
|
|
67
|
+
if (prototype.componentWillReact) {
|
|
36
68
|
throw new Error("The componentWillReact life-cycle event is no longer supported")
|
|
37
69
|
}
|
|
38
70
|
if (componentClass["__proto__"] !== PureComponent) {
|
|
39
|
-
if (!
|
|
40
|
-
|
|
41
|
-
} else if (
|
|
71
|
+
if (!prototype.shouldComponentUpdate) {
|
|
72
|
+
prototype.shouldComponentUpdate = observerSCU
|
|
73
|
+
} else if (prototype.shouldComponentUpdate !== observerSCU) {
|
|
42
74
|
// n.b. unequal check, instead of existence check, as @observer might be on superclass as well
|
|
43
75
|
throw new Error(
|
|
44
76
|
"It is not allowed to use shouldComponentUpdate in observer based components."
|
|
@@ -46,146 +78,153 @@ export function makeClassComponentObserver(
|
|
|
46
78
|
}
|
|
47
79
|
}
|
|
48
80
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
// (otherwise the computed wouldn't update and become stale on props change, since props are not observable)
|
|
52
|
-
// However, this solution is not without it's own problems: https://github.com/mobxjs/mobx-react/issues?utf8=%E2%9C%93&q=is%3Aissue+label%3Aobservable-props-or-not+
|
|
53
|
-
makeObservableProp(target, "props")
|
|
54
|
-
makeObservableProp(target, "state")
|
|
55
|
-
if (componentClass.contextType) {
|
|
56
|
-
makeObservableProp(target, "context")
|
|
81
|
+
if (__DEV__) {
|
|
82
|
+
Object.defineProperties(prototype, observablePropDescriptors)
|
|
57
83
|
}
|
|
58
84
|
|
|
59
|
-
const originalRender =
|
|
85
|
+
const originalRender = prototype.render
|
|
60
86
|
if (typeof originalRender !== "function") {
|
|
61
|
-
const displayName = getDisplayName(
|
|
87
|
+
const displayName = getDisplayName(componentClass)
|
|
62
88
|
throw new Error(
|
|
63
89
|
`[mobx-react] class component (${displayName}) is missing \`render\` method.` +
|
|
64
90
|
`\n\`observer\` requires \`render\` being a function defined on prototype.` +
|
|
65
91
|
`\n\`render = () => {}\` or \`render = function() {}\` is not supported.`
|
|
66
92
|
)
|
|
67
93
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
94
|
+
|
|
95
|
+
prototype.render = function () {
|
|
96
|
+
Object.defineProperty(this, "render", {
|
|
97
|
+
// There is no safe way to replace render, therefore it's forbidden.
|
|
98
|
+
configurable: false,
|
|
99
|
+
writable: false,
|
|
100
|
+
value: isUsingStaticRendering()
|
|
101
|
+
? originalRender
|
|
102
|
+
: createReactiveRender.call(this, originalRender)
|
|
103
|
+
})
|
|
72
104
|
return this.render()
|
|
73
105
|
}
|
|
74
|
-
patch(target, "componentDidMount", function () {
|
|
75
|
-
this[mobxIsUnmounted] = false
|
|
76
|
-
if (!this.render[mobxAdminProperty]) {
|
|
77
|
-
// Reaction is re-created automatically during render, but a component can re-mount and skip render #3395.
|
|
78
|
-
// To re-create the reaction and re-subscribe to relevant observables we have to force an update.
|
|
79
|
-
Component.prototype.forceUpdate.call(this)
|
|
80
|
-
}
|
|
81
|
-
})
|
|
82
|
-
patch(target, "componentWillUnmount", function () {
|
|
83
|
-
if (isUsingStaticRendering()) {
|
|
84
|
-
return
|
|
85
|
-
}
|
|
86
106
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const displayName = getDisplayName(this)
|
|
95
|
-
console.warn(
|
|
96
|
-
`The reactive render of an observer class component (${displayName})
|
|
97
|
-
was overridden after MobX attached. This may result in a memory leak if the
|
|
98
|
-
overridden reactive render was not properly disposed.`
|
|
107
|
+
const originalComponentDidMount = prototype.componentDidMount
|
|
108
|
+
prototype.componentDidMount = function () {
|
|
109
|
+
if (__DEV__ && this.componentDidMount !== Object.getPrototypeOf(this).componentDidMount) {
|
|
110
|
+
const displayName = getDisplayName(componentClass)
|
|
111
|
+
throw new Error(
|
|
112
|
+
`[mobx-react] \`observer(${displayName}).componentDidMount\` must be defined on prototype.` +
|
|
113
|
+
`\n\`componentDidMount = () => {}\` or \`componentDidMount = function() {}\` is not supported.`
|
|
99
114
|
)
|
|
100
115
|
}
|
|
101
116
|
|
|
102
|
-
|
|
117
|
+
// `componentDidMount` may not be called at all. React can abandon the instance after `render`.
|
|
118
|
+
// That's why we use finalization registry to dispose reaction created during render.
|
|
119
|
+
// Happens with `<Suspend>` see #3492
|
|
120
|
+
//
|
|
121
|
+
// `componentDidMount` can be called immediately after `componentWillUnmount` without calling `render` in between.
|
|
122
|
+
// Happens with `<StrictMode>`see #3395.
|
|
123
|
+
//
|
|
124
|
+
// If `componentDidMount` is called, it's guaranteed to run synchronously with render (similary to `useLayoutEffect`).
|
|
125
|
+
// Therefore we don't have to worry about external (observable) state being updated before mount (no state version checking).
|
|
126
|
+
//
|
|
127
|
+
// Things may change: "In the future, React will provide a feature that lets components preserve state between unmounts"
|
|
128
|
+
|
|
129
|
+
const admin = getAdministration(this)
|
|
130
|
+
|
|
131
|
+
admin.mounted = true
|
|
132
|
+
|
|
133
|
+
// Component instance committed, prevent reaction disposal.
|
|
134
|
+
observerFinalizationRegistry.unregister(admin)
|
|
135
|
+
|
|
136
|
+
// We don't set forceUpdate before mount because it requires a reference to `this`,
|
|
137
|
+
// therefore `this` could NOT be garbage collected before mount,
|
|
138
|
+
// preventing reaction disposal by FinalizationRegistry and leading to memory leak.
|
|
139
|
+
// As an alternative we could have `admin.instanceRef = new WeakRef(this)`, but lets avoid it if possible.
|
|
140
|
+
admin.forceUpdate = () => this.forceUpdate()
|
|
141
|
+
|
|
142
|
+
if (!admin.reaction) {
|
|
143
|
+
// 1. Instance was unmounted (reaction disposed) and immediately remounted without running render #3395.
|
|
144
|
+
// 2. Reaction was disposed by finalization registry before mount. Shouldn't ever happen for class components:
|
|
145
|
+
// `componentDidMount` runs synchronously after render, but our registry are deferred (can't run in between).
|
|
146
|
+
// In any case we lost subscriptions to observables, so we have to create new reaction and re-render to resubscribe.
|
|
147
|
+
// The reaction will be created lazily by following render.
|
|
148
|
+
admin.forceUpdate()
|
|
149
|
+
}
|
|
150
|
+
return originalComponentDidMount?.apply(this, arguments)
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// TODO@major Overly complicated "patch" is only needed to support the deprecated @disposeOnUnmount
|
|
154
|
+
patch(prototype, "componentWillUnmount", function () {
|
|
155
|
+
if (isUsingStaticRendering()) {
|
|
156
|
+
return
|
|
157
|
+
}
|
|
158
|
+
const admin = getAdministration(this)
|
|
159
|
+
admin.reaction?.dispose()
|
|
160
|
+
admin.reaction = null
|
|
161
|
+
admin.forceUpdate = null
|
|
162
|
+
admin.mounted = false
|
|
103
163
|
})
|
|
164
|
+
|
|
104
165
|
return componentClass
|
|
105
166
|
}
|
|
106
167
|
|
|
107
168
|
// Generates a friendly name for debugging
|
|
108
|
-
function getDisplayName(
|
|
109
|
-
return
|
|
110
|
-
comp.displayName ||
|
|
111
|
-
comp.name ||
|
|
112
|
-
(comp.constructor && (comp.constructor.displayName || comp.constructor.name)) ||
|
|
113
|
-
"<component>"
|
|
114
|
-
)
|
|
169
|
+
function getDisplayName(componentClass: ComponentClass) {
|
|
170
|
+
return componentClass.displayName || componentClass.name || "<component>"
|
|
115
171
|
}
|
|
116
172
|
|
|
117
173
|
function createReactiveRender(originalRender: any) {
|
|
118
|
-
/**
|
|
119
|
-
* If props are shallowly modified, react will render anyway,
|
|
120
|
-
* so atom.reportChanged() should not result in yet another re-render
|
|
121
|
-
*/
|
|
122
|
-
setHiddenProp(this, skipRenderKey, false)
|
|
123
|
-
/**
|
|
124
|
-
* forceUpdate will re-assign this.props. We don't want that to cause a loop,
|
|
125
|
-
* so detect these changes
|
|
126
|
-
*/
|
|
127
|
-
setHiddenProp(this, isForcingUpdateKey, false)
|
|
128
|
-
|
|
129
|
-
const initialName = getDisplayName(this)
|
|
130
174
|
const boundOriginalRender = originalRender.bind(this)
|
|
131
175
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
const createReaction = () => {
|
|
135
|
-
const reaction = new Reaction(`${initialName}.render()`, () => {
|
|
136
|
-
if (!isRenderingPending) {
|
|
137
|
-
// N.B. Getting here *before mounting* means that a component constructor has side effects (see the relevant test in misc.test.tsx)
|
|
138
|
-
// This unidiomatic React usage but React will correctly warn about this so we continue as usual
|
|
139
|
-
// See #85 / Pull #44
|
|
140
|
-
isRenderingPending = true
|
|
141
|
-
if (this[mobxIsUnmounted] !== true) {
|
|
142
|
-
let hasError = true
|
|
143
|
-
try {
|
|
144
|
-
setHiddenProp(this, isForcingUpdateKey, true)
|
|
145
|
-
if (!this[skipRenderKey]) {
|
|
146
|
-
Component.prototype.forceUpdate.call(this)
|
|
147
|
-
}
|
|
148
|
-
hasError = false
|
|
149
|
-
} finally {
|
|
150
|
-
setHiddenProp(this, isForcingUpdateKey, false)
|
|
151
|
-
if (hasError) {
|
|
152
|
-
reaction.dispose()
|
|
153
|
-
// Forces reaction to be re-created on next render
|
|
154
|
-
this.render[mobxAdminProperty] = null
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
reaction["reactComponent"] = this
|
|
161
|
-
return reaction
|
|
162
|
-
}
|
|
176
|
+
const admin = getAdministration(this)
|
|
163
177
|
|
|
164
178
|
function reactiveRender() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
179
|
+
if (!admin.reaction) {
|
|
180
|
+
// Create reaction lazily to support re-mounting #3395
|
|
181
|
+
admin.reaction = createReaction(admin)
|
|
182
|
+
if (!admin.mounted) {
|
|
183
|
+
// React can abandon this instance and never call `componentDidMount`/`componentWillUnmount`,
|
|
184
|
+
// we have to make sure reaction will be disposed.
|
|
185
|
+
observerFinalizationRegistry.register(this, admin, this)
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
let error: unknown = undefined
|
|
190
|
+
let renderResult = undefined
|
|
191
|
+
admin.reaction.track(() => {
|
|
171
192
|
try {
|
|
172
193
|
// TODO@major
|
|
173
194
|
// Optimization: replace with _allowStateChangesStart/End (not available in mobx@6.0.0)
|
|
174
|
-
|
|
195
|
+
renderResult = _allowStateChanges(false, boundOriginalRender)
|
|
175
196
|
} catch (e) {
|
|
176
|
-
|
|
197
|
+
error = e
|
|
177
198
|
}
|
|
178
199
|
})
|
|
179
|
-
if (
|
|
180
|
-
throw
|
|
200
|
+
if (error) {
|
|
201
|
+
throw error
|
|
181
202
|
}
|
|
182
|
-
return
|
|
203
|
+
return renderResult
|
|
183
204
|
}
|
|
184
205
|
|
|
185
206
|
return reactiveRender
|
|
186
207
|
}
|
|
187
208
|
|
|
188
|
-
function
|
|
209
|
+
function createReaction(admin: ObserverAdministration) {
|
|
210
|
+
return new Reaction(`${admin.name}.render()`, () => {
|
|
211
|
+
if (!admin.mounted) {
|
|
212
|
+
// This is neccessary to avoid react warning about calling forceUpdate on component that isn't mounted yet.
|
|
213
|
+
// This happens when component is abandoned after render - our reaction is already created and reacts to changes.
|
|
214
|
+
// Due to the synchronous nature of `componenDidMount`, we don't have to worry that component could eventually mount and require update.
|
|
215
|
+
return
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
try {
|
|
219
|
+
admin.forceUpdate?.()
|
|
220
|
+
} catch (error) {
|
|
221
|
+
admin.reaction?.dispose()
|
|
222
|
+
admin.reaction = null
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function observerSCU(nextProps: ClassAttributes<any>, nextState: any): boolean {
|
|
189
228
|
if (isUsingStaticRendering()) {
|
|
190
229
|
console.warn(
|
|
191
230
|
"[mobx-react] It seems that a re-rendering of a React component is triggered while in static (server-side) mode. Please make sure components are rendered only once server-side."
|
|
@@ -202,45 +241,24 @@ function observerSCU(nextProps: React.ClassAttributes<any>, nextState: any): boo
|
|
|
202
241
|
return !shallowEqual(this.props, nextProps)
|
|
203
242
|
}
|
|
204
243
|
|
|
205
|
-
function
|
|
206
|
-
|
|
207
|
-
const atomHolderKey = newSymbol(`reactProp_${propName}_atomHolder`)
|
|
208
|
-
function getAtom() {
|
|
209
|
-
if (!this[atomHolderKey]) {
|
|
210
|
-
setHiddenProp(this, atomHolderKey, createAtom("reactive " + propName))
|
|
211
|
-
}
|
|
212
|
-
return this[atomHolderKey]
|
|
213
|
-
}
|
|
214
|
-
Object.defineProperty(target, propName, {
|
|
244
|
+
function createObservablePropDescriptor(key: "props" | "state" | "context") {
|
|
245
|
+
return {
|
|
215
246
|
configurable: true,
|
|
216
247
|
enumerable: true,
|
|
217
|
-
get
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
248
|
+
get() {
|
|
249
|
+
const admin = getAdministration(this)
|
|
250
|
+
const derivation = _getGlobalState().trackingDerivation
|
|
251
|
+
if (derivation && derivation !== admin.reaction) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`[mobx-react] Cannot read "${admin.name}.${key}" in a reactive context, as it isn't observable.
|
|
254
|
+
Please use component lifecycle method to copy the value into a local observable first.
|
|
255
|
+
See https://github.com/mobxjs/mobx/blob/main/packages/mobx-react/README.md#note-on-using-props-and-state-in-derivations`
|
|
256
|
+
)
|
|
224
257
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
// Why this check? BC?
|
|
228
|
-
// @ts-expect-error
|
|
229
|
-
if (_allowStateReadsStart && _allowStateReadsEnd) {
|
|
230
|
-
_allowStateReadsEnd(prevReadState)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return this[valueHolderKey]
|
|
258
|
+
return admin[key]
|
|
234
259
|
},
|
|
235
|
-
set
|
|
236
|
-
|
|
237
|
-
setHiddenProp(this, valueHolderKey, v)
|
|
238
|
-
setHiddenProp(this, skipRenderKey, true)
|
|
239
|
-
getAtom.call(this).reportChanged()
|
|
240
|
-
setHiddenProp(this, skipRenderKey, false)
|
|
241
|
-
} else {
|
|
242
|
-
setHiddenProp(this, valueHolderKey, v)
|
|
243
|
-
}
|
|
260
|
+
set(value) {
|
|
261
|
+
getAdministration(this)[key] = value
|
|
244
262
|
}
|
|
245
|
-
}
|
|
263
|
+
}
|
|
246
264
|
}
|
package/src/utils/utils.ts
CHANGED
|
@@ -1,21 +1,3 @@
|
|
|
1
|
-
let symbolId = 0
|
|
2
|
-
function createSymbol(name: string): symbol | string {
|
|
3
|
-
if (typeof Symbol === "function") {
|
|
4
|
-
return Symbol(name)
|
|
5
|
-
}
|
|
6
|
-
const symbol = `__$mobx-react ${name} (${symbolId})`
|
|
7
|
-
symbolId++
|
|
8
|
-
return symbol
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const createdSymbols = {}
|
|
12
|
-
export function newSymbol(name: string): symbol | string {
|
|
13
|
-
if (!createdSymbols[name]) {
|
|
14
|
-
createdSymbols[name] = createSymbol(name)
|
|
15
|
-
}
|
|
16
|
-
return createdSymbols[name]
|
|
17
|
-
}
|
|
18
|
-
|
|
19
1
|
export function shallowEqual(objA: any, objB: any): boolean {
|
|
20
2
|
//From: https://github.com/facebook/fbjs/blob/c69904a511b900266935168223063dd8772dfc40/packages/fbjs/src/core/shallowEqual.js
|
|
21
3
|
if (is(objA, objB)) {
|
|
@@ -96,8 +78,8 @@ export function setHiddenProp(target: object, prop: any, value: any): void {
|
|
|
96
78
|
* Utilities for patching componentWillUnmount, to make sure @disposeOnUnmount works correctly icm with user defined hooks
|
|
97
79
|
* and the handler provided by mobx-react
|
|
98
80
|
*/
|
|
99
|
-
const mobxMixins =
|
|
100
|
-
const mobxPatchedDefinition =
|
|
81
|
+
const mobxMixins = Symbol("patchMixins")
|
|
82
|
+
const mobxPatchedDefinition = Symbol("patchedDefinition")
|
|
101
83
|
|
|
102
84
|
export interface Mixins extends Record<string, any> {
|
|
103
85
|
locks: number
|
|
@@ -175,6 +157,7 @@ function createDefinition(
|
|
|
175
157
|
let wrappedFunc = wrapFunction(originalMethod, mixins)
|
|
176
158
|
|
|
177
159
|
return {
|
|
160
|
+
// @ts-ignore
|
|
178
161
|
[mobxPatchedDefinition]: true,
|
|
179
162
|
get: function () {
|
|
180
163
|
return wrappedFunc
|