From bd148fe6a71395ca2bef320075d83eb989a95e58 Mon Sep 17 00:00:00 2001 From: "chengcheng.guo" Date: Tue, 6 Sep 2022 21:03:10 +0800 Subject: [PATCH] Fix #3492: throw warning when use class component and suspense --- .changeset/chilly-nails-own.md | 5 ++ packages/mobx-react-lite/src/index.ts | 5 ++ .../src/utils/reactionCleanupTracking.ts | 2 + packages/mobx-react/src/observerClass.ts | 49 +++++++++++++++++-- 4 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 .changeset/chilly-nails-own.md diff --git a/.changeset/chilly-nails-own.md b/.changeset/chilly-nails-own.md new file mode 100644 index 000000000..0dd5266a3 --- /dev/null +++ b/.changeset/chilly-nails-own.md @@ -0,0 +1,5 @@ +--- +"mobx-react": patch +--- + +Fix #3492: throw warning when use class component and suspense diff --git a/packages/mobx-react-lite/src/index.ts b/packages/mobx-react-lite/src/index.ts index b493ee1e5..fcdd70024 100644 --- a/packages/mobx-react-lite/src/index.ts +++ b/packages/mobx-react-lite/src/index.ts @@ -15,6 +15,11 @@ export { useLocalObservable } from "./useLocalObservable" export { useLocalStore } from "./useLocalStore" export { useAsObservableSource } from "./useAsObservableSource" export { resetCleanupScheduleForTests as clearTimers } from "./utils/reactionCleanupTracking" +export { + addReactionToTrack, + reactionToTrackSymbol, + recordReactionAsCommitted +} from "./utils/reactionCleanupTracking" export function useObserver(fn: () => T, baseComponentName: string = "observed"): T { if ("production" !== process.env.NODE_ENV) { diff --git a/packages/mobx-react-lite/src/utils/reactionCleanupTracking.ts b/packages/mobx-react-lite/src/utils/reactionCleanupTracking.ts index 1aeffbd33..656ed1bc0 100644 --- a/packages/mobx-react-lite/src/utils/reactionCleanupTracking.ts +++ b/packages/mobx-react-lite/src/utils/reactionCleanupTracking.ts @@ -3,6 +3,8 @@ import { createReactionCleanupTrackingUsingFinalizationRegister } from "./create import { createTimerBasedReactionCleanupTracking } from "./createTimerBasedReactionCleanupTracking" export { IReactionTracking } from "./reactionCleanupTrackingCommon" +export const reactionToTrackSymbol = Symbol.for("reactionToTrackSymbol") + const { addReactionToTrack, recordReactionAsCommitted, diff --git a/packages/mobx-react/src/observerClass.ts b/packages/mobx-react/src/observerClass.ts index 7a2c6be1a..fb62327c8 100644 --- a/packages/mobx-react/src/observerClass.ts +++ b/packages/mobx-react/src/observerClass.ts @@ -1,4 +1,4 @@ -import { PureComponent, Component } from "react" +import { PureComponent, Component, createRef } from "react" import { createAtom, _allowStateChanges, @@ -10,6 +10,11 @@ import { import { isUsingStaticRendering } from "mobx-react-lite" import { newSymbol, shallowEqual, setHiddenProp, patch } from "./utils/utils" +import { + addReactionToTrack, + reactionToTrackSymbol, + recordReactionAsCommitted +} from "mobx-react-lite" const mobxAdminProperty = $mobx || "$mobx" // BC const mobxObserverProperty = newSymbol("isMobXReactObserver") @@ -66,6 +71,9 @@ export function makeClassComponentObserver( ) } target.render = function () { + if (!this[reactionToTrackSymbol]) { + this[reactionToTrackSymbol] = createRef() + } this.render = isUsingStaticRendering() ? originalRender : createReactiveRender.call(this, originalRender) @@ -73,7 +81,30 @@ export function makeClassComponentObserver( } patch(target, "componentDidMount", function () { this[mobxIsUnmounted] = false - if (!this.render[mobxAdminProperty]) { + recordReactionAsCommitted(this[reactionToTrackSymbol]) + let hasUpdated = false + if (this[reactionToTrackSymbol].current) { + this[reactionToTrackSymbol].current.mounted = true + if (this[reactionToTrackSymbol].current.changedBeforeMount) { + this[reactionToTrackSymbol].current.changedBeforeMount = false + hasUpdated = true + Component.prototype.forceUpdate.call(this) + } + !this.render[mobxAdminProperty] + } else { + const initialName = getDisplayName(this) + this[reactionToTrackSymbol].current = { + reaction: new Reaction(`${initialName}.render()`, () => { + Component.prototype.forceUpdate.call(this) + }), + mounted: true, + changedBeforeMount: false, + cleanAt: Infinity + } + hasUpdated = true + Component.prototype.forceUpdate.call(this) + } + if (!this.render[mobxAdminProperty] && !hasUpdated) { // Reaction is re-created automatically during render, but a component can re-mount and skip render #3395. // To re-create the reaction and re-subscribe to relevant observables we have to force an update. Component.prototype.forceUpdate.call(this) @@ -84,6 +115,8 @@ export function makeClassComponentObserver( return } + this[reactionToTrackSymbol].current = null + const reaction = this.render[mobxAdminProperty] if (reaction) { reaction.dispose() @@ -143,13 +176,18 @@ function createReactiveRender(originalRender: any) { try { setHiddenProp(this, isForcingUpdateKey, true) if (!this[skipRenderKey]) { - Component.prototype.forceUpdate.call(this) + if (this[reactionToTrackSymbol].current.mounted) { + Component.prototype.forceUpdate.call(this) + } else { + this[reactionToTrackSymbol].current.changedBeforeMount = true + } } hasError = false } finally { setHiddenProp(this, isForcingUpdateKey, false) if (hasError) { reaction.dispose() + this[reactionToTrackSymbol].current = null // Forces reaction to be re-created on next render this.render[mobxAdminProperty] = null } @@ -165,6 +203,11 @@ function createReactiveRender(originalRender: any) { isRenderingPending = false // Create reaction lazily to support re-mounting #3395 const reaction = (reactiveRender[mobxAdminProperty] ??= createReaction()) + + if (!this[reactionToTrackSymbol].current) { + addReactionToTrack(this[reactionToTrackSymbol], reaction, {}) + } + let exception: unknown = undefined let rendering = undefined reaction.track(() => {