diff --git a/src/api/configure.ts b/src/api/configure.ts index f01e7cd55..e99328107 100644 --- a/src/api/configure.ts +++ b/src/api/configure.ts @@ -9,6 +9,7 @@ import { export function configure(options: { enforceActions?: boolean | "strict" | "never" | "always" | "observed" computedRequiresReaction?: boolean + computedConfigurable?: boolean isolateGlobalState?: boolean disableErrorBoundaries?: boolean reactionScheduler?: (f: () => void) => void @@ -16,6 +17,7 @@ export function configure(options: { const { enforceActions, computedRequiresReaction, + computedConfigurable, disableErrorBoundaries, reactionScheduler } = options @@ -52,6 +54,9 @@ export function configure(options: { if (computedRequiresReaction !== undefined) { globalState.computedRequiresReaction = !!computedRequiresReaction } + if (computedConfigurable !== undefined) { + globalState.computedConfigurable = !!computedConfigurable + } if (disableErrorBoundaries !== undefined) { if (disableErrorBoundaries === true) console.warn( diff --git a/src/core/globalstate.ts b/src/core/globalstate.ts index a0cb6e445..ee129aeaa 100644 --- a/src/core/globalstate.ts +++ b/src/core/globalstate.ts @@ -101,6 +101,12 @@ export class MobXGlobals { */ computedRequiresReaction = false + /** + * Allows overwriting of computed properties, useful in tests but not prod as it can cause + * memory leaks. See https://github.com/mobxjs/mobx/issues/1867 + */ + computedConfigurable = false + /* * Don't catch and rethrow exceptions. This is useful for inspecting the state of * the stack when an exception occurs while debugging. @@ -108,7 +114,7 @@ export class MobXGlobals { disableErrorBoundaries = false /* - * If true, we are already handling an exception in an action. Any errors in reactions should be supressed, as + * If true, we are already handling an exception in an action. Any errors in reactions should be supressed, as * they are not the cause, see: https://github.com/mobxjs/mobx/issues/1836 */ suppressReactionErrors = false diff --git a/src/types/observableobject.ts b/src/types/observableobject.ts index bef78cdf1..05db9de62 100644 --- a/src/types/observableobject.ts +++ b/src/types/observableobject.ts @@ -385,7 +385,7 @@ export function generateComputedPropConfig(propName) { return ( computedPropertyConfigs[propName] || (computedPropertyConfigs[propName] = { - configurable: false, // See https://github.com/mobxjs/mobx/issues/1867, for computeds, we don't want reconfiguration, as this will potentially leak memory! + configurable: globalState.computedConfigurable, enumerable: false, get() { return getAdministrationForComputedPropOwner(this).read(propName) diff --git a/test/base/strict-mode.js b/test/base/strict-mode.js index 57362742a..17bcf982e 100644 --- a/test/base/strict-mode.js +++ b/test/base/strict-mode.js @@ -249,3 +249,25 @@ test("#1869", function() { }).toThrow("Since strict-mode is enabled") mobx._resetGlobalState() // should preserve strict mode }) + +test("allow overwriting computed if configured", function() { + try { + mobx.configure({ computedConfigurable: true }) + const x = mobx.observable({ + v: 2, + get multiplied() { + return x * 2 + } + }) + mobx.decorate(x, { multiplied: mobx.computed }) + + expect(() => { + Object.defineProperty(x, "multiplied", { + value: 12 + }) + }).not.toThrow() + expect(x.multiplied).toBe(12) + } finally { + mobx.configure({ computedConfigurable: false }) + } +})