Skip to content

Commit 44a2cf4

Browse files
authored
Better React 18 support (#3590)
1 parent 8bea8cd commit 44a2cf4

26 files changed

+611
-396
lines changed

.changeset/brown-seals-worry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"mobx": minor
3+
---
4+
5+
Better support for React 18: Mobx now keeps track of a global state version, which updates with each mutation.

.changeset/early-terms-bow.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"mobx-react-lite": major
3+
---
4+
5+
Components now use `useSyncExternalStore`, which should prevent tearing - you have to update mobx, otherwise it should behave as previously.<br>
6+
Improved displayName/name handling as discussed in #3438.<br>

.changeset/wise-waves-jam.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"mobx-react": major
3+
---
4+
5+
Functional components now use `useSyncExternalStore`, which should prevent tearing - you have to update mobx, otherwise it should behave as previously.<br>
6+
Improved displayName/name handling of functional components as discussed in #3438.<br>
7+
Reactions of uncommited class components are now correctly disposed, fixes #3492.<br>
8+
Reactions don't notify uncommited class components, avoiding the warning, fixes #3492.<br>
9+
Removed symbol "polyfill" and replaced with actual Symbols.<br>
10+
Removed `this.render` replacement detection + warning. `this.render` is no longer configurable/writable (possibly BC).<br>
11+
Class component instance is no longer exposed as `component[$mobx]["reactcomponent"]` (possibly BC).<br>
12+
Deprecated `disposeOnUnmount`, it's not compatible with remounting.<br>

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
{
22
"[typescript]": {
3+
"editor.defaultFormatter": "esbenp.prettier-vscode",
34
"editor.formatOnSave": true,
45
"editor.formatOnPaste": false
56
},
67
"[javascript]": {
8+
"editor.defaultFormatter": "esbenp.prettier-vscode",
79
"editor.formatOnSave": true,
810
"editor.formatOnPaste": false
911
},

packages/mobx-react-lite/__tests__/api.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ test("correct api should be exposed", function () {
1818
"useObserver",
1919
"isObserverBatched",
2020
"observerBatching",
21-
"useStaticRendering"
21+
"useStaticRendering",
22+
"_observerFinalizationRegistry"
2223
].sort()
2324
)
2425
})

packages/mobx-react-lite/__tests__/observer.test.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -618,12 +618,15 @@ it("should hoist known statics only", () => {
618618
expect(wrapped.render).toBe(undefined)
619619
})
620620

621-
it("should have the correct displayName", () => {
622-
const TestComponent = observer(function MyComponent() {
621+
it("should inherit original name/displayName #3438", () => {
622+
function Name() {
623623
return null
624-
})
624+
}
625+
Name.displayName = "DisplayName"
626+
const TestComponent = observer(Name)
625627

626-
expect((TestComponent as any).type.displayName).toBe("MyComponent")
628+
expect((TestComponent as any).type.name).toBe("Name")
629+
expect((TestComponent as any).type.displayName).toBe("DisplayName")
627630
})
628631

629632
test("parent / childs render in the right order", done => {

packages/mobx-react-lite/__tests__/strictAndConcurrentModeUsingFinalizationRegistry.test.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { cleanup, render } from "@testing-library/react"
1+
import { cleanup, render, waitFor } from "@testing-library/react"
22
import * as mobx from "mobx"
33
import * as React from "react"
44
import { useObserver } from "../src/useObserver"
@@ -43,10 +43,17 @@ test("uncommitted components should not leak observations", async () => {
4343

4444
// Allow gc to kick in in case to let finalization registry cleanup
4545
gc()
46-
await sleep(50)
47-
48-
// count1 should still be being observed by Component1,
49-
// but count2 should have had its reaction cleaned up.
50-
expect(count1IsObserved).toBeTruthy()
51-
expect(count2IsObserved).toBeFalsy()
46+
// Can take a while (especially on CI) before gc actually calls the registry
47+
await waitFor(
48+
() => {
49+
// count1 should still be being observed by Component1,
50+
// but count2 should have had its reaction cleaned up.
51+
expect(count1IsObserved).toBeTruthy()
52+
expect(count2IsObserved).toBeFalsy()
53+
},
54+
{
55+
timeout: 2000,
56+
interval: 200
57+
}
58+
)
5259
})

packages/mobx-react-lite/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
"homepage": "https://mobx.js.org",
3939
"dependencies": {},
4040
"peerDependencies": {
41-
"mobx": "^6.1.0",
41+
"mobx": "^6.9.0",
4242
"react": "^16.8.0 || ^17 || ^18"
4343
},
4444
"peerDependenciesMeta": {
@@ -51,7 +51,8 @@
5151
},
5252
"devDependencies": {
5353
"mobx": "^6.8.0",
54-
"expose-gc": "^1.0.0"
54+
"expose-gc": "^1.0.0",
55+
"use-sync-external-store": "^1.2.0"
5556
},
5657
"keywords": [
5758
"mobx",

packages/mobx-react-lite/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export { useLocalObservable } from "./useLocalObservable"
1616
export { useLocalStore } from "./useLocalStore"
1717
export { useAsObservableSource } from "./useAsObservableSource"
1818

19+
export { observerFinalizationRegistry as _observerFinalizationRegistry }
1920
export const clearTimers = observerFinalizationRegistry["finalizeAllImmediately"] ?? (() => {})
2021

2122
export function useObserver<T>(fn: () => T, baseComponentName: string = "observed"): T {

packages/mobx-react-lite/src/observer.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,13 @@ export function observer<P extends object, TRef = {}>(
104104
return useObserver(() => render(props, ref), baseComponentName)
105105
}
106106

107-
// Don't set `displayName` for anonymous components,
108-
// so the `displayName` can be customized by user, see #3192.
109-
if (baseComponentName !== "") {
110-
;(observerComponent as React.FunctionComponent).displayName = baseComponentName
111-
}
107+
// Inherit original name and displayName, see #3438
108+
;(observerComponent as React.FunctionComponent).displayName = baseComponent.displayName
109+
Object.defineProperty(observerComponent, "name", {
110+
value: baseComponent.name,
111+
writable: true,
112+
configurable: true
113+
})
112114

113115
// Support legacy context: `contextTypes` must be applied before `memo`
114116
if ((baseComponent as any).contextTypes) {
@@ -136,7 +138,7 @@ export function observer<P extends object, TRef = {}>(
136138
set() {
137139
throw new Error(
138140
`[mobx-react-lite] \`${
139-
this.displayName || this.type?.displayName || "Component"
141+
this.displayName || this.type?.displayName || this.type?.name || "Component"
140142
}.contextTypes\` must be set before applying \`observer\`.`
141143
)
142144
}

0 commit comments

Comments
 (0)