Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MobX: Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed

My context looks like this:

class AuthStoreClass {
    authUser = null

    constructor() {
        makeAutoObservable(this)
    }

    login = async (params) => {
        const { data: { data: authUser } } = await loginUser(params)
        this.authUser = authUser
    }
}

const AuthStoreContext = React.createContext(null);

export const authStoreObject = new AuthStoreClass()

export const AuthStoreProvider = ({ children }: any) => {
    return <AuthStoreContext.Provider value={authStoreObject}>{children}</AuthStoreContext.Provider>;
};
export const useAuthStore = () => {
    return React.useContext(AuthStoreContext);
};

And I am using the context somewhere else in a component:

const LoginPage = observer(() => {
    const authStore = useAuthStore()
    ...
    authStore.login(...)

The last line reports the following warning:

[MobX] Since strict-mode is enabled, changing (observed) observable values without using an action is not allowed. Tried to modify: [email protected]

Everything works as expected. How can I fix this issue?

like image 297
mleister Avatar asked Nov 10 '20 14:11

mleister


People also ask

What is action in MobX?

An action is any piece of code that modifies the state. In principle, actions always happen in response to an event. For example, a button was clicked, some input changed, a websocket message arrived, etc. MobX requires that you declare your actions, although makeAutoObservable can automate much of this job.

How does MobX observable work?

MobX reacts to any existing observable property that is read during the execution of a tracked function. "reading" is dereferencing an object's property, which can be done through "dotting into" it (eg. user.name ) or using the bracket notation (eg. user['name'] , todos[3] ) or destructuring (eg.

What is observable in MobX?

observable defines a trackable field that stores the state. action marks a method as action that will modify the state. computed marks a getter that will derive new facts from the state and cache its output.

Is MobX synchronous?

Mobx autorun is synchronous normally, but becomes non-synchronous when an action is running #1911.


2 Answers

Your login function is async and you need to use runInAction inside, or handle result in a separate action, or use some other way of handling async actions:

import { runInAction, makeAutoObservable } from "mobx"

class AuthStoreClass {
    authUser = null

    constructor() {
        makeAutoObservable(this)
    }

    login = async (params) => {
        const { data: { data: authUser } } = await loginUser(params)
        
        // Wrap all changes with runInAction
        runInAction(() => {
          this.authUser = authUser
        })

        // or do it in separate function
        this.setUser(authUser)
    }

    // This method will be wrapped into `action` automatically by `makeAutoObservable`
    setUser = (user) => {
        this.authUser = authUser
    }
}

More about async actions (you can even use generators!): https://mobx.js.org/actions.html#asynchronous-actions

In MobX version 6 actions are enforced by default but you can disable warnings with configure method:

import { configure } from "mobx"

configure({
    enforceActions: "never",
})

But be careful doing it though, the goal of enforceActions is that you don't forget to wrap event handlers and all mutations in an action. Not doing it might cause extra re-runs of your observers. For example, if you changing two values inside some handler without action then your component might re-render twice instead of once. makeAutoObservable wraps all methods automatically but you still need to handle async methods and Promises manually.

like image 187
Danila Avatar answered Oct 18 '22 18:10

Danila


You can also change the function to use the yield syntax, negating the need for runInAction.

*login() {
    const { data: { data: authUser } } = yield loginUser(params)
    this.authUser = authUser
}

like image 1
Rob Avatar answered Oct 18 '22 20:10

Rob