diff --git a/CHANGELOG.md b/CHANGELOG.md index 262e5fa4..d56e1a05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # MobX-React Changelog +### 6.1.5 + +- Added warning for attempting to modify a component class' render function after a MobX reaction has already attached. Helps prevent memory leaks as in: [#797](/~https://github.com/mobxjs/mobx-react/issues/797) + ### 6.1.4 - Update dependency mobx-react-lite@1.4.2 which includes fix for [RN Fast Refresh](/~https://github.com/mobxjs/mobx-react-lite/issues/226) diff --git a/src/observerClass.js b/src/observerClass.js index 0e4022c9..6271ddd9 100644 --- a/src/observerClass.js +++ b/src/observerClass.js @@ -35,12 +35,29 @@ export function makeClassComponentObserver(componentClass) { } patch(target, "componentWillUnmount", function() { if (isUsingStaticRendering() === true) return - this.render[mobxAdminProperty] && this.render[mobxAdminProperty].dispose() + if (this.render[mobxAdminProperty]) { + this.render[mobxAdminProperty].dispose() + } else if (process.env.NODE_ENV !== "production") { + const displayName = getDisplayName(this) + console.warn( + `The render function for an observer component (${displayName}) was modified after MobX attached. This is not supported, since the new function can't be triggered by MobX.` + ) + } this[mobxIsUnmounted] = true }) return componentClass } +// Generates a friendly name for debugging +function getDisplayName(comp) { + return ( + comp.displayName || + comp.name || + (comp.constructor && (comp.constructor.displayName || comp.constructor.name)) || + "" + ) +} + function makeComponentReactive(render) { if (isUsingStaticRendering() === true) return render.call(this) @@ -55,12 +72,7 @@ function makeComponentReactive(render) { */ setHiddenProp(this, isForcingUpdateKey, false) - // Generate friendly name for debugging - const initialName = - this.displayName || - this.name || - (this.constructor && (this.constructor.displayName || this.constructor.name)) || - "" + const initialName = getDisplayName(this) const baseRender = render.bind(this) let isRenderingPending = false diff --git a/test/observer.test.js b/test/observer.test.js index ff90b056..b4cf6aa8 100644 --- a/test/observer.test.js +++ b/test/observer.test.js @@ -838,3 +838,33 @@ test.skip("#709 - applying observer on React.memo component", () => { // eslint-disable-next-line no-undef render(, { wrapper: ErrorCatcher }) }) + +test("#797 - replacing this.render should trigger a warning", () => { + @observer + class Component extends React.Component { + render() { + return
+ } + swapRenderFunc() { + this.render = () => { + return + } + } + } + + const compRef = React.createRef() + const { unmount } = render() + compRef.current.swapRenderFunc() + + const msg = [] + const warn_orig = console.warn + console.warn = m => msg.push(m) + + unmount() + + expect(msg.length).toBe(1) + expect(msg[0]).toBe( + `The render function for an observer component (Component) was modified after MobX attached. This is not supported, since the new function can't be triggered by MobX.` + ) + console.warn = warn_orig +})