Introduction to Reactive Programming using MobX

Talin
Machine Words
11 min readMar 5, 2019

--

Reactive Programming is a powerful technique for handling interactive and asynchronous tasks. The name “reactive” comes from the fact that your code automatically “reacts” to changes in the data.

The simplest way to think about this is to imagine a spreadsheet. When you type a formula into a cell, it references data in other cells. When the data in those cells change, the formula automatically recalculates, displaying the newly computed value. You, as the user, don’t need to do anything special to cause the formula to recalculate.

The classic approach to reactive data

In traditional programming, this sort of thing is done using an observer pattern, first described in the book Design Patterns by Gamma et al. This pattern consists of an observable object, that is, some object that contains data of interest, and which also maintains a list of subscribers called observers. When the data changes, the observable object notifies all of the observers that are currently subscribed.

Although simple in concept, in practice the observer pattern can be tedious: each observer has to manually subscribe and unsubscribe to the observable. If you forget to unsubscribe (a common bug), then you may get a memory leak. Another problem is that, in the observable, you have to write code to detect whenever any data changes, and call the function to notify all of the observers. This complicates the internal logic of the observable.

The amount of effort involved in setting up observers means that programmers typically try to keep the number of observers and observables to a minimum. For example, given a data structure such as a user profile, rather than making each individual field observable (which is too much work), it’s fairly typical to just make the entire object an observable. This means that observers who are interested in just one field will get notified whenever any field changes, leading to unnecessary recalculations and updates.

Annotations to the rescue

Modern reactive frameworks such as MobX relieve at lot of this work by automatically adding the logic for observers and observables using annotations.

(Although MobX is a JavaScript framework, the same techniques can be done in any programming language that supports annotations or metaprogramming techniques.)

To declare a property as observable, use the @observable annotation. The following example shows how a hypothetical “UserAccount” class would look with observable properties:

import { observable } from 'mobx';class UserAccount {
@observable public username: string;
@observable public displayName: string;
@observable public photo: string;
}

(Note: The example above is written in TypeScript, as will all the examples in this article.)

In this example, we’re declaring each of the three properties of the object as observable. That means that observers who are only interested in the value of one property won’t be notified when the other properties change.

Reactions

Now that we’ve defined our observables, how do we react to them? MobX defines many different kinds of ways to observe a property, but the simplest method is called autorun(). The autorun() function takes a single function as it’s argument, and re-runs that function whenever the data changes:

const user = new UserAccount();autorun(() => {
console.log(user.displayName);
});

In this example, we create a user account object. We then call autorun() with a simple function that merely prints out the display name of the user. This function argument will be called once, immediately, and will be called again each time we assign a new value to user.displayName.

The net result is that it will print the user’s display name once when the program first starts, and then each time the display name changes it will print the new value. This process of re-running the code is called a reaction.

MobX is effectively acting like a spreadsheet: you changed some data, and it re-evaluates the formula that makes use of that data. That is the essence of reactive programming.

You may be wondering, how does MobX know which properties to react to, since we didn’t explicitly tell autorun() to listen to changes to the displayName property? The answer is that autorun() records the fact that we used displayName in the reaction function. In other words, the very act of reading the variable is enough to let autorun() know that we are interested in it.

How it works

You might also be curious as to what, exactly, the @observable annotation does and how it works. Essentially, it replaces the observable property with a ‘getter’ method of the same name. That getter function keeps a record of which “tasks” or “actions” accessed the variable. There is also a setter which notifies all of those tasks. So for our UserAccount example, the code for displayName was replaced with something like this:

class UserAccount {
public get displayName(): string {
// get the current task and record the fact that it
// observed this variable
}
public set displayName(value: string) {
// Update the value, and then re-run any tasks that
// were recorded by the getter.
}
}

In this case, what I mean by a ‘task’ is some computation (such as the function we passed to autorun()) which is evaluated repeatedly in reaction to some data changing.

Note that if you read an observable variable from outside of a task, you’ll still get the value of the variable, but it won’t be reactive — the code won’t automatically re-run. Reactivity only happens within the context of an autorun() or something like it.

MobX and React.js

MobX can work with a number of different UI frameworks such as Facebook’s React.js. (Note the similarity in the name — React.js is so named because it also has some reactive programming features, but they are not automatic to extent that they are in MobX).

These two frameworks work very well together, as shown in the following example:

import * as React from 'react';
import { observer } from 'mobx-react';
interface Props {
user: UserAccount;
}
@observer
export class UserName extends React.Component<Props> {
public render() {
const { user } = this.props;
return (
<span className="user-name">{user.displayName}</span>
);
}
}

This is a simple React component that takes a UserAccount object and displays the user’s display name inside of a <span> tag. However, what’s special about this component is the the @observer annotation (note: not the same as @observable!) This annotation causes the entire component to become an observer, which reacts to changes in the data by re-rendering, as if it had a hidden internal autorun() wrapped around the render method. That means that any time the displayName property changes, the component will redraw itself with the new display name.

While this might not seem terribly impressive at first thought, consider that in a web site such as a chat application or a bug tracker, a user’s name might appear many times on the same page, all rendered by different instances of this same UserName component. That means that when the name changes, all of those components will redraw — without you having to do anything!

Even better, only those components will redraw! Other parts of the page which were not reacting to the changed name will not have to re-render themselves. This includes the “parent” components that contained the UserName component.

Although this point is subtle, it hints at the power of a framework like MobX: by making it easy to declare observability at the level of individual properties, page updates and re-renders can be fine-grained. In fact, it enables a coding style in which you have “micro” view components, where each component renders just one or two observable properties, keeping redrawing to an absolute minimum.

TypeScript and MobX

One of the things I particularly like about MobX is how easy it is to use with TypeScript compared to some other state management frameworks like Redux. You may have noticed that when we added the annotation to the displayName property, the actual type of the variable (‘string’) was unaffected. We didn’t have to perform heroic acts of generic templating in order to get the correct type, we just used our data structures in the natural way. From a standpoint of maintaining strict type safety, the change was essentially effortless.

Derived properties using @computed

One thing that separates truly powerful programming concepts from mere curiosities or fads is whether they can be composed — that is, whether you can combine the basic units together to create more complex and capable units.

MobX allows observers to be composed using the @computed annotation, which is used to create an observable which is calculated from another observable.

The easiest way to think of this is to go back to our spreadsheet example: sometimes you have a cell that contains a formula, and the cell uses data from other cells that also contains formulas. This means that the formula in the cells is “derived” from other formulas, and the formula will recalculate whenever any of the formulas that it depends on recalculate.

Here’s a concrete example that I use in my own work: say you want to display a table of project members, represented by UserAccount objects. You’d like the table to automatically redraw whenever a person is added or removed from the project. So far so good — we can just make the list of project members an @observable.

However, you’d also like to allow users to click on the column headers in the table, indicating that they want to display the rows sorted in order by the data in that column. We can do that by making the ‘sort key’ an observable as well.

We also need the sorted list of rows, which depends both on the raw row data, as well as the sort order. So our data model looks like this:

import { observable, computed } from 'mobx';// A sortable list of users
class UserList {
@observable public sortKey: string;
@observable public users: UserAccount[];
@computed
public get sorted(): UserAccount[] {
// Make a copy of the array
const result = this.users.slice();
// Use the sort key to get a comparator function.
// Sort the array using the comparator.
result.sort(getComparator(this.sortKey));
return result;
}
}

@computed creates a new observable, in this case derived from both the users observable and the sortKey observable (since both of those variables were used in the method). We can then use userList.sorted to render our table, which means that if either the sort order changes, or the row data changes, the table component will re-render.

There’s another nifty thing that @computed does for us: it memoizes the computed result. In other words, when the calculation is performed, it will do a deep comparison to see if the output result is the same as the previous result. If it is the same, then observers won’t be re-run.

What would this mean in our example? Well suppose the first user has a username of “arobinson” and a display name of “Amy Robinson”. And the second user has a username of “barry” and a display name of “Barry Kent”. Now suppose we click on the column header for “display name”. The data will be sorted and the table will re-draw, displaying Amy in the first row and Barry in the second row. Now, suppose we click again, this time on the “username” column. Well, in this case the order didn’t actually change — the sorted list is the same in either case. So the table won’t redraw, because it doesn’t need to.

Asynchronous Programming: Throttling

Now I am going to show a short but fairly advanced example which will really give you a taste for just how powerful reactive programming can be.

Let’s say you have a JavaScript program that makes a bunch of HTTP requests to a server. However, you know that the browser gets unhappy if you try and open too many network connections at the same time, so you’d like to limit the number of simultaneous requests. Let’s say we want to limit it to no more than 4 requests at a time.

There are a bunch of ways we could do this. For example, we could write a task queue with a fixed-sized worker pool, but that’s a lot of work. We could use a promise semaphore, or we could download an npm work queue package, but those have a lot of magic hidden under the hood. Isn’t there a simple way to do this that is transparent, obvious, and easy to understand?

In fact, there is. We’re going to use another MobX function, this one is called when() and it is similar to autorun(). What when() does is that it waits for a condition to be true, and then runs a function when that happens. What’s special about this is that the condition is an observable — meaning that when() only checks the condition when something changes, by ‘reacting’ to it.

The call signature for when() looks like this:

function when(condition_function, action_function);

The first argument is a function that returns a boolean. This function should depend on one or more observables; when() will re-check the condition as those observables change.

The second argument is the action function, and will be run once (and once only) when the condition function returns true.

In our throttling example, we can use when() to delay starting a request to the server if there are already too many requests in progress. We do this by simply keeping a count of the number of active requests, which will, of course, be an observable.

Here’s the complete example:

import { observable, when } from 'mobx;class ThrottledRequest {
@observable private numRequests = 0;
public makeRequest(url: string) {
when(
() => this.numRequests < 4,
() => {
this.numRequests += 1;
sendHTTPRequest(url).then(() => {
this.numRequests -= 1;
}
}
);
}
}

Although this example is very short, it’s doing a lot.

The first time you call makeRequest(), the number of active requests will be zero, so the condition (numRequests < 4) is true, and thus the server request is started immediately. This increases the request count by 1.

If you continue to call makeRequest(), the request count will continue to increase until it gets to 4. At this point, any additional requests will have to wait, because the condition is no longer true. Note, however, that makeRequest() still returns immediately in either case — it doesn’t block.

However, as the requests that are already in progress finish, they will invoke the .then() callback, which decrements the request count. Each time this happens, one of the requests that are waiting will be allowed to go forward.

Essentially what we have done here is we’ve built a semaphore — but we’ve built it out of smaller, simpler, easy to understand parts. Other than the reactive framework, there’s nothing magical or hidden about this example, all of the state is out in the open. An average CS student could probably re-invent this in an afternoon, given the same set of tools.

What I especially like about this example is that it shows how easy it is open up a conceptual world of non-linear, asynchronous programming, where you are no longer thinking of your code as steps that are executed in a specific sequential order, but instead are reacting to events and conditions as they happen.

Wrapping Up

There’s a ton of things I’ve glossed over or left out entirely; I haven’t talked about how to cancel an autorun(), or all of the extra options you can pass to when(), or what happens when your task function reads different observables at different times (it can get tricky). It may take a while for you to really wrap your head around the reactive way of thinking.

If you want to dive into this further, I highly recommend going to the MobX site and reading the introductory documentation.

For me, personally, I use MobX at work every day; my company’s product uses it heavily, including some of its most advanced features, and I have never regretted choosing it.

See Also

--

--

Talin
Machine Words

I’m not a mad scientist. I’m a mad natural philosopher.