From db3ff5b0f1a9bd09ce58b9bf465a87fec18c4aef Mon Sep 17 00:00:00 2001 From: Michel Weststrate Date: Tue, 27 Feb 2018 11:16:14 +0100 Subject: [PATCH] Introduced `mobx.configure({ warnOnUnsafeComputationReads: true })`, implements #961 --- CHANGELOG.md | 2 ++ src/api/configure.ts | 8 +++++++- src/core/computedvalue.ts | 6 ++++++ src/core/globalstate.ts | 13 ++++++++++++- test/base/strict-mode.js | 18 ++++++++++++++++++ test/utils/test-utils.js | 18 ++++++++++++++++++ 6 files changed, 63 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92701b4ae..eb2b3c7b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,9 +6,11 @@ * `values`, `keys`, `set`, `remove` to use objects as observable collections * Introduced `onBecomeObserved` and `onBecomeUnobserved` * MobX now supports development only checks and exceptions, resulting in smaller and faster production builds. The setup requirements are identical to react +* Introduced `mobx.configure({ warnOnUnsafeComputationReads: true })` ## Breaking changes +* `useStrict(boolean)` was dropped, use `configure({enforceActions: boolean})` instead * `expr` is moved to mobx-utils. Remember, `expr(fn)` is just sugar for `computed(fn).get()` * `createTransformer` is moved to mobx-utils * Passing `context` explicitly to `autorun`, `reaction` etc is no longer supported. Use arrow functions or function.bind instead. diff --git a/src/api/configure.ts b/src/api/configure.ts index ab6942fbb..eede8cbe1 100644 --- a/src/api/configure.ts +++ b/src/api/configure.ts @@ -1,8 +1,14 @@ import { globalState } from "../core/globalstate" -export function configure(options: { enforceActions?: boolean }): void { +export function configure(options: { + enforceActions?: boolean + warnOnUnsafeComputationReads?: boolean +}): void { if (options.enforceActions !== undefined) { globalState.enforceActions = !!options.enforceActions globalState.allowStateChanges = !options.enforceActions } + if (options.warnOnUnsafeComputationReads) { + globalState.warnOnUnsafeComputationReads = !!options.warnOnUnsafeComputationReads + } } diff --git a/src/core/computedvalue.ts b/src/core/computedvalue.ts index c73ee4508..70849c791 100644 --- a/src/core/computedvalue.ts +++ b/src/core/computedvalue.ts @@ -130,6 +130,12 @@ export class ComputedValue implements IObservable, IComputedValue, IDeriva .name}' is being read outside a reactive context and doing a full recompute` ) } + if (globalState.warnOnUnsafeComputationReads) { + console.warn( + `[mobx] Computed value ${this + .name} is read outside a reactive context and not actively observed. Doing a full recompute` + ) + } this.value = this.computeValue(false) } endBatch() diff --git a/src/core/globalstate.ts b/src/core/globalstate.ts index d9dc8555d..c7c10681d 100644 --- a/src/core/globalstate.ts +++ b/src/core/globalstate.ts @@ -6,7 +6,13 @@ import { IObservable } from "./observable" /** * These values will persist if global state is reset */ -const persistentKeys = ["mobxGuid", "spyListeners", "enforceActions", "runId"] +const persistentKeys = [ + "mobxGuid", + "spyListeners", + "enforceActions", + "warnOnUnsafeComputationReads", + "runId" +] export class MobXGlobals { /** @@ -79,6 +85,11 @@ export class MobXGlobals { * Globally attached error handlers that react specifically to errors in reactions */ globalReactionErrorHandlers: ((error: any, derivation: IDerivation) => void)[] = [] + + /** + * Warn if computed values are accessed outside a reactive context + */ + warnOnUnsafeComputationReads = false } export let globalState: MobXGlobals = new MobXGlobals() diff --git a/test/base/strict-mode.js b/test/base/strict-mode.js index f7b0645ea..cb9e2bcc6 100644 --- a/test/base/strict-mode.js +++ b/test/base/strict-mode.js @@ -1,4 +1,5 @@ var mobx = require("../../src/mobx.ts") +var utils = require("../utils/test-utils") var strictError = /Since strict-mode is enabled, changing observed observable values outside actions is not allowed. Please wrap the code in an `action` if this change is intended. Tried to modify: / @@ -184,3 +185,20 @@ test("strict mode checks", function() { mobx._resetGlobalState() d() }) + +test("warn on unsafe reads", function() { + try { + mobx.configure({ warnOnUnsafeComputationReads: true }) + const x = mobx.observable({ + y: 3, + get yy() { + return this.y * 2 + } + }) + utils.consoleWarn(() => { + x.yy + }, /is read outside a reactive context and not actively observed/) + } finally { + mobx.configure({ warnOnUnsafeComputationReads: false }) + } +}) diff --git a/test/utils/test-utils.js b/test/utils/test-utils.js index ced9f66c3..ba300c6f8 100644 --- a/test/utils/test-utils.js +++ b/test/utils/test-utils.js @@ -18,6 +18,24 @@ exports.consoleError = function(block, regex) { expect(messages).toMatch(regex) } +exports.consoleWarn = function(block, regex) { + let messages = "" + const orig = console.warn + console.warn = function() { + Object.keys(arguments).forEach(key => { + messages += ", " + arguments[key] + }) + messages += "\n" + } + try { + block() + } finally { + console.warn = orig + } + expect(messages.length).toBeGreaterThan(0) + expect(messages).toMatch(regex) +} + exports.supressConsole = function(block) { const { warn, error } = console Object.assign(console, {