Josh
Josh

Reputation: 7415

Can't get MobX, TypeScript, React and actions to work

I've been trying to get what seems like a simple example going but haven't been able to, using MobX, React, TypeScript and actions. Without strict, everything works, but I would rather use strict mode.

The error I'm getting is:

Uncaught Error: [mobx] 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: [email protected]
    at invariant (mobx.module.js:2704)
    at fail$1 (mobx.module.js:2699)
    at checkIfStateModificationsAreAllowed (mobx.module.js:3303)
    at ObservableValue.prepareNewValue (mobx.module.js:997)
    at ObservableObjectAdministration.write (mobx.module.js:1093)
    at AppState.set [as timer] (mobx.module.js:1257)
    at AppState.set [as timer] (mobx.module.js:143)
    at new AppState (index.tsx:26)
    at eval (index.tsx:66)

Unfortunately I'm not sure where I am supposed to put @action (or @action.bound) to make it work, or what I'm doing wrong...

This is my code:

import * as React from 'react';
import * as ReactDOM from 'react-dom';
import * as MobX from 'mobx';
import * as MobXReact from 'mobx-react';
import DevTools from 'mobx-react-devtools';

MobX.configure({
    enforceActions: 'strict'
});

class AppState {
    @MobX.observable
    public timer = 0;

    public constructor() {
        setInterval(() => {
            this.incrTimer();
        }, 1000);
    }

    @MobX.action
    public incrTimer() {
        this.timer += 1;
    }

    @MobX.action
    public resetTimer() {
        this.timer = 0;
    }
}

@MobXReact.observer
class TimerView extends React.Component<{appState: AppState}, {}> {
    render() {
        return (
            <div>
                <button onClick={this.onReset}>
                    Seconds passed: {this.props.appState.timer}
                </button>
                <DevTools/>
            </div>
        );
    }

    onReset = () => {
        this.props.appState.resetTimer();
    }
};

const appState = new AppState();
const rootNode = document.body.appendChild(document.createElement('div'));

ReactDOM.render(<TimerView appState={appState} />, rootNode);

Update: After more tinkering based on the answers, it turns out that the error was coming from the JS that TS emitted. The public timer = 0; property was being emitted by TS as this.timer = 0; inside constructor, which is what was blowing up.

Removing the assignment and adding another function got past the error, but doing that for property initialization seems like it shouldn't be necessary.

Upvotes: 1

Views: 1021

Answers (1)

Chris Edgington
Chris Edgington

Reputation: 1467

Very likely the problem is that you are setting up the timer in the constructor. MobX is not wrapping your properties until your constructor is done, so when your constructor sets up this timer callback, its getting a reference to the real incrTimer, not the mobx.action(incrTimer). If you move your timer install to a method outside of the constructor and then call that after your object is constructed, this strict warning will go away. For example:

const appState = new AppState();
appState.setupTimer();

Upvotes: 1

Related Questions