{"record":{"id":"6e4b992f01522358","repo":"mobxjs/mobx","slug":"mobx-since-strict-mode-is-enabled-changing-obs","errorCode":null,"errorMessage":"[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed. Tried to modify: ${atom.name_}","messagePattern":"\\[MobX\\] Since strict-mode is enabled, changing \\(observed\\) observable values without using an action is not allowed\\. Tried to modify: (.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"packages/mobx/src/core/derivation.ts","lineNumber":143,"sourceCode":"        }\n    }\n}\n\nexport function isComputingDerivation() {\n    return globalState.trackingDerivation !== null // filter out actions inside computations\n}\n\nexport function checkIfStateModificationsAreAllowed(atom: IAtom) {\n    if (!__DEV__) {\n        return\n    }\n    const hasObservers = !!atom.observers_ && atom.observers_.size > 0\n    // Should not be possible to change observed state outside strict mode, except during initialization, see #563\n    if (\n        !globalState.allowStateChanges &&\n        (hasObservers || globalState.enforceActions === \"always\")\n    ) {\n        console.warn(\n            \"[MobX] \" +\n                (globalState.enforceActions\n                    ? \"Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed. Tried to modify: \"\n                    : \"Side effects like changing state are not allowed at this point. Are you trying to modify state from, for example, a computed value or the render function of a React component? You can wrap side effects in 'runInAction' (or decorate functions with 'action') if needed. Tried to modify: \") +\n                atom.name_\n        )\n    }\n}\n\nexport function checkIfStateReadsAreAllowed(observable: IObservable) {\n    if (__DEV__ && !globalState.allowStateReads && globalState.observableRequiresReaction) {\n        console.warn(\n            `[mobx] Observable '${observable.name_}' being read outside a reactive context.`\n        )\n    }\n}\n\n/**","sourceCodeStart":125,"sourceCodeEnd":161,"githubUrl":"https://github.com/mobxjs/mobx/blob/01211a698b64ea94de8e5f276ff8235fcf8ddf96/packages/mobx/src/core/derivation.ts#L125-L161","documentation":"MobX strict mode (enforceActions) forbids mutating observed observables outside of an action. checkIfStateModificationsAreAllowed fires from mutators like set_, delete, addValue_, spliceWithArray_ and defineObservableProperty_ when globalState.allowStateChanges is false and the atom has observers or enforceActions is 'always'. It warns (console.warn) naming the observable via atom.name_ so you know which piece of state was mutated illegally.","triggerScenarios":"Calling observable.set(), array.push/splice, map.set/delete, or addObservable/addValue outside an action while configure({enforceActions: 'observed'|'always'}) is active and the atom has observers (or enforceActions==='always'). Also mutating state inside a computed or render, where allowStateChanges is false.","commonSituations":"Async callbacks (setTimeout, promise .then) updating store fields after an action ended; React event handlers writing directly to stores with enforceActions:'always'; migrating from MobX 4/5 defaults where mutations outside actions were allowed; forgetting runInAction after await inside an action.","solutions":["Wrap the mutation in runInAction(() => { ... }) at the mutation site.","Move the mutation into an @action.bound method or action() wrapper on the store.","For async flows, put every post-await assignment inside runInAction, since the action scope ends at the await.","If mutations are intentional and safe, relax with configure({ enforceActions: 'never' }) — discouraged.","Use makeAutoObservable with autoAction or flow() for generators doing async state updates."],"exampleFix":"// before (enforceActions enabled)\nasync function loadUser() {\n  const user = await fetchUser()\n  store.user = user // warns: changing state outside action\n}\n\n// after\nimport { runInAction } from 'mobx'\nasync function loadUser() {\n  const user = await fetchUser()\n  runInAction(() => {\n    store.user = user\n  })\n}","handlingStrategy":"validation","validationCode":"import { configure, isAction } from 'mobx'\n// ensure strict-mode config matches your mutation style:\n// configure({ enforceActions: 'observed' })\nfunction assertMutatedInsideAction(mutator) {\n  if (!mutator._isMobxAction) {\n    throw new Error('State mutation must be wrapped in an action (enforceActions is enabled)')\n  }\n  return mutator\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always wrap post-await assignments in runInAction.","Use flow() for async generator-style actions.","Make all store mutators @action.bound so call sites can't bypass actions.","Keep enforceActions: 'observed' in dev to catch violations early."],"tags":["mobx","strict-mode","enforceactions","state-mutation","reactivity"],"backgroundTag":"mobx-strict-mode-state-change-outside-action","analyzedSha":"01211a698b64ea94de8e5f276ff8235fcf8ddf96","analyzedAt":"2026-08-28T22:10:08.831Z","schemaVersion":2},"datasetVersion":"2026-08-29T02:17:18.158Z"}