DEV Community

Kay Gosho
Kay Gosho

Posted on

MobX Tips: New Api named flow (confusing name...)

3.x

In MobX 3.x, Writing async actions is a little verbose.

import { observable, runInAction } from 'mobx'
import { someApi } from '../api'

class SomeStore {
    @observable someState = ''

    async someAction() {
        const res = await someApi.fetch()
        runInAction(() => {
            this.someState = res.data
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

4.x

MobX 4.x has a new API named flow, which is really confusing. Every time I google mobx flow, Facebook's static typing tool shows up.

import { observable, flow } from 'mobx'
import { someApi } from '../api'

class SomeStore {
    @observable someState = ''

    someAction = flow(function * () {
        this.someState = yield someApi.fetch()
    })
}
Enter fullscreen mode Exit fullscreen mode

Ohnestly I don't know what is happening above, but the code become shorter and cooler with generator function and class property.

If we use using MobX's flow + TypeScript with strict mode, try bind this to the class in generator function, like this:

    public someAction = flow(function * (this: SomeStore) {
        this.someState = yield someApi.fetch()
    })
Enter fullscreen mode Exit fullscreen mode

(I spend one hour to find this solution)

Promise

If we consider error handling, Promise become simpler:

import { observable, action } from 'mobx'
import { someApi } from '../api'

class SomeStore {
    @observable someState = ''
    @observable error = null

    someAction() {
        someApi.fetch().then(
            action(res => this.someState = res.data),
            action(err => this.err = err),
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (4)

Collapse
 
schovi profile image
David Schovanec

Hello,

just to clarify your examples (and my knowledge of that):
In the first example with async/await you don't need to wrap it in runInAction
It can be simple as with flow.

    async someAction() {
        this.someState = await someApi.fetch()
    }

runInAction just batch all changes inside the anonymous function and trigger only one update at the end of that function. And because there is just one it is not needed.

But. Flow with generator function does this behind scene because of generator function runs blocks of code between each yield separately and thus it is automatically wrapped in runInAction.

Maybe extend your examples with more setters to make it more clear (add loader, error etc) :)

Collapse
 
acro5piano profile image
Kay Gosho

Thank you for the comment, David!

I do not have to use runInAction in this use case right?
Maybe that's true.

Actually I wrote multiple MobX state change like this with mobx.useStrict(true):

    @action
    async someAction() {
        this.loading = true
        const someState = await someApi.fetch()
        runInAction(() => this.someState = someState)
    }

as you pointed out, runInAction wrap action(fn)() with friendly syntax, while flow and yield block the state change in async flow.
When I faced the MobX's cation of @action and runInAction, I started to use runInAction so I would have to understand how MobX change states.

Anyway your comment improve my comprehension of MobX!
Thanks again.

Collapse
 
schovi profile image
David Schovanec

I lied to you before :( I didn't read it correctly.
It would work as expected because there is just one setter and mobx can handle that.
But when we turn on mobx.configure({ enforceActions: true }) it would fail. And it would fail even with @action async someAction() {....} because await is asynchronous and outside the original @action function. In this example this.someState = await waits to await. So your original example was correct :)

It really depends on many things (enforceActions: true, @async decorator, etc ...). Great explanation on this (new for me) page mobx.js.org/best/actions.html :)

Thread Thread
 
acro5piano profile image
Kay Gosho

I lied to you before :( I didn't read it correctly.

No, I did not write enforceActions: true (nor useStrict(), old api) in my article. It's my fault, though thank you for clarification ;)

As you mentioned, both await and Promise wrap new function which change MobX's state so runInAction, otherwise MobX shows caution.