Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using the MobX @action decorator with async functions and .then

I'm using MobX 2.2.2 to try to mutate state inside an async action. I have MobX's useStrict set to true.

@action someAsyncFunction(args) {
  fetch(`http://localhost:8080/some_url`, {
    method: 'POST',
    body: {
      args
    }
  })
  .then(res => res.json())
  .then(json => this.someStateProperty = json)
  .catch(error => {
    throw new Error(error)
  });
}

I get:

Error: Error: [mobx] Invariant failed: It is not allowed to create or change state outside an `action` when MobX is in strict mode. Wrap the current method in `action` if this state change is intended

Do I need to supply the @action decorator to the second .then statement? Any help would be appreciated.

like image 882
twsmith Avatar asked May 31 '16 20:05

twsmith


2 Answers

Do I need to supply the @action decorator to the second .then statement? Any help would be appreciated.

This is pretty close to the actual solution.

.then(json => this.someStateProperty = json)

should be

.then(action(json => this.someStateProperty = json))

Keep in mind action can be called in many ways that aren't exclusive to @action. From the docs on action:

  • action(fn)
  • action(name, fn)
  • @action classMethod
  • @action(name) classMethod
  • @action boundClassMethod = (args) => { body }
  • @action(name) boundClassMethod = (args) => { body }

are all valid ways to mark a function as an action.

Here's a bin demonstrating the solution: http://jsbin.com/peyayiwowu/1/edit?js,output

mobx.useStrict(true);
const x = mobx.observable(1);

// Do async stuff
function asyncStuff() {
  fetch('http://jsonplaceholder.typicode.com/posts')
    .then((response) => response.json())
    // .then((objects) => x.set(objects[0])) BREAKS
    .then(mobx.action((objects) => x.set(objects[0])))
}

asyncStuff()

As for why your error actually happens I'm guessing that the top level @action doesn't recursively decorate any functions as actions inside the function it's decorating, meaning your anonymous function passed into your promise wasn't really an action.

like image 94
m0meni Avatar answered Oct 02 '22 17:10

m0meni


To complement the above answer; indeed, action only works on the function you pass to it. The functions in the then are run on a separate stack and should therefor be recognizable as separate actions.

Note that you can also give the actions a name as well so that you easily recognize them in the devtools if you use those:

then(action("update objects after fetch", json => this.someStateProperty = json))

like image 33
mweststrate Avatar answered Oct 02 '22 16:10

mweststrate