reaction()

reaction() is yet another kind of reaction in MobX. Yes, the choice of the API name was intentional. reaction() is similar to autorun() but waits for a change in the observables before executing the effect-function. reaction() in fact takes two arguments, which are as follows:

reaction(tracker-function, effect-function): disposer-function

tracker-function: () => data, effect-function: (data) => {}

tracker-function is where all the observables are tracked. Any time the tracked observables change, it will re-execute. It is supposed to return a value that is used to compare it to the previous run of tracker-function. If these return-values differ, the effect-function is executed.

By breaking up the activity of a reaction into a change-detecting function (tracker function) and the effect function, reaction() gives us more fine-grained control over when a side-effect should be caused. It is no longer just dependent on the observables it is tracking inside the tracker function. Instead, it now depends on the data returned by the tracker function. The effect function receives this data in its input. Any observables used in the effect function are not tracked.

Just like autorun(), you also get a disposer function as the return-value of reaction(). This can be used to cancel the side-effect anytime you want.

We can put this into practice with an example. Let's say you want to be notified anytime an item in your Cart changes its price. After all, you don't want to purchase something that suddenly shoots up in price. At the same time, you don't want to miss out on a great deal as well. So, getting a notification when the price changes is a useful thing to have. We can implement this by using reaction(), as shown here:

import { observable, action, reaction } from 'mobx';

class Cart {
@observable modified = new Date();
@observable items = [];

cancelPriceTracker = null;

trackPriceChangeForItem(name) {
if (this.cancelPriceTracker) {
this.cancelPriceTracker();
}

// 1. Reaction to track price changes
this.cancelPriceTracker = reaction(
() => {
const item = this.items.find(x => x.name === name);
return item ? item.price : null;
},
price => {
console.log(`Price changed for ${name}: ${price !==
null ? price : 0}`);
},
);
}

@action
addItem(name, price) {
this.items.push({ name, price });
this.modified = new Date();
}

@action
changePrice(name, price) {
const item = this.items.find(x => x.name === name);
if (item) {
item.price = price;
}
}
}

const cart = new Cart();

cart.addItem('Shoes', 20);

// 2. Now track price for "Shoes"
cart.trackPriceChangeForItem('Shoes');

// 3. Change the price
cart.changePrice('Shoes', 100);
cart.changePrice('Shoes', 50);

// Prints:
// Price changed for Shoes: 100
// Price changed for Shoes: 50

In the preceding snippet, we are setting up a price tracker in comment 1, as a reaction to track price changes. Notice that it takes two functions as inputs. The first function (tracker-function) finds the item with the given name and returns its price as the output of the tracker function. Any time it changes, the corresponding effect function is executed.

The console logs also print only when the price changes. This is exactly the behavior we wanted and achieved through a reaction(). Now that you are notified of the price changes, you can make better buying decisions.