问题
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.
回答1:
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
.
回答2:
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))
回答3:
note that in async method you manualy have to start a new action/transaction after awaiting something:
@mobx.action async someAsyncFunction(args) {
this.loading = true;
var result = await fetch(`http://localhost:8080/some_url`, {
method: 'POST',
body: {
args
}
});
var json = await result.json();
@mobx.runInAction(()=> {
this.someStateProperty = json
this.loading = false;
});
}
my preference
I prefer to not use @mobx.action/runInAction directly but always place it on an private
method. And let public methods call private methods that actually update the state:
public someAsyncFunction(args) {
this.startLoading();
return fetch(`http://localhost:8080/some_url`, {
method: 'POST',
body: {
args
}
})
.then(res => res.json())
.then(this.onFetchResult);
}
@mobx.action
private startLoading = () => {
this.loading = true;
}
@mobx.action
private onFetchResult = (json) => {
this.someStateProperty = json;
this.loading = false;
}
来源:https://stackoverflow.com/questions/37554828/using-the-mobx-action-decorator-with-async-functions-and-then