Actions
action(fn)
action(name, fn)
@action classMethod() {}
@action(name) classMethod() {}
@action boundClassMethod = (args) => { body }
@action(name) boundClassMethod = (args) => { body }
@action.bound classMethod() {}
@action.bound(function() {})
Any application has actions. Actions are anything that modify the state. With MobX, you can make it explicit in your code where your actions live by marking them. Actions help you to structure your code better.
It takes a function and returns a function with the same signature, but wrapped with transaction
, untracked
, and allowStateChanges
. Especially the fact that transaction
is applied automatically yields great performance benefits; actions will batch mutations and only notify computed values and reactions after the (outer most) action has finished. This make sure intermediate or incomplete values produced during an action are not visible to the rest of the application until the action has finished.
It is advised to use (@)action
on any function that modifies observables or has side effects. action
also provides useful debugging information in combination with the devtools.
Using the @action
decorator with ES 5.1 setters (i.e. @action set propertyName
) is not supported; however, setters of computed properties are automatically actions.
Note:
Using
action
is mandatory when strict mode is enabled.
Two example actions from the contact-list
project:
@action createRandomContact() {
this.pendingRequestCount++;
superagent
.get('https://randomuser.me/api/')
.set('Accept', 'application/json')
.end(action("createRandomContact-callback", (error, results) => {
// ^ Note: asynchronous callbacks are separate actions!
if (error)
console.error(error);
else {
const data = JSON.parse(results.text).results[0];
const contact = new Contact(this, data.dob, data.name, data.login.username, data.picture)
contact.addTag('random-user');
this.contacts.push(contact);
this.pendingRequestCount--;
}
}));
}
When to Use Actions?
Actions should only, and always, be used on functions that modify state. Functions that just perform look-ups, filters, etc. should not be marked as actions; to allow MobX to track their invocations.
Note:
In the above example, writing asynchronous actions is as straight-forward as marking all the callbacks as
action
as well. Beyond that there is nothing special about asynchronous processes in MobX; an asynchronous update is just a synchronous action call in some future.
Strict mode enforces that all state modifications are done by an action. This is a useful best practice in larger, long-term code bases. Simply call mobx.useStrict(true)
when your application is initialized, and MobX will throw anytime you (accidentally) try to modify state without using an action.
Bound Actions
The action
decorator / function follows the normal rules for binding in JavaScript. However, MobX 3 introduces action.bound
to automatically bind actions to the targeted object.
Note:
Unlike
action
,(@)action.bound
does not take a name parameter, so the name will always be based on the property name to which the action is bound.
class Ticker {
@observable this.tick = 0
@action.bound
increment() {
this.tick++ // 'this' will always be correct
}
}
const ticker = new Ticker()
setInterval(ticker.increment, 1000)
const ticker = observable({
tick: 1,
increment: action.bound(function() {
this.tick++ // bound 'this'
})
})
setInterval(ticker.increment, 1000)
Note:
Don't use
action.bound
with arrow functions; arrow functions are already bound and cannot be rebound.
runInAction(name?, thunk)
For one-time actions runInAction(name?, fn, scope?)
can be used, which is sugar for action(name, fn, scope)()
.
runInAction
is a simple utility that takes a code block and executes it in an (anonymous) action. This is useful to create and execute actions on the fly, for example inside an asynchronous process.