This post is meant to be a revised version of my previous MobX + React Tutorial. I was only going to edit it initially and add in a note that it had been updated. But then I realized I’d pretty much be rewriting the whole article all over again, because of the whole “No Decorators” thing. And so this happened.

Oh yeah and SEO is also something that exists.

Our MobX + React Application.

We’re going to be building an App called a List Detail View. The App will render out a list of one line “summary” components for larger pieces of data. Additionally, clicking one of them will render a second component containing more detailed information.

In this way you click on a list element to view its details. List Detail View. Simple enough right?

Mobx + React Tutorial

Now in the previous post this used to be an embed from my Pen at Codepen.io. And it allowed me to make the following outrageous claim, which is still kind of true:

We’ll be writing code so concise that it wouldn’t be out-of-place for it to be in a single file.

Still though, it’s been a few years, I’m older now. Please never do that. Never have all your code in one file. Never.

That claim though is just one of the great things about MobX, it being very light weight. It’s never going to weight you down with a bunch of structure you’re unsure that you even need. Unlike some *cough* other frameworks *cough*.

Note: Redux’s structure becomes a MASSIVE upside when you’re doing work with a large team. Repeat after me: Big teams need structure.

Now, there used to be two ways of following this tutorial. But now we only have one, so make sure to branch off of the following project:

It has been updated to 2019 in terms of packages. Although, to be realistic, I would’ve much preferred using create-react-app instead of web-pack. But this will do.

Now that we have common ground to start on, the first thing we have to do is create the view side of our app.

The React Side of our application.

Word of advice, for these few sections focusing on React, I am expecting you to have some general knowledge on the topic. If you don’t, my article on React Fiber is surprisingly still a great place to start getting educated on the topic. I’m quite amazed actually.

I repeat, I will not be explaining myself every step of the way until we hit the MobX part. However, I will only be pointing out design aspects and things I find interesting.

Something to note before we start:

The first comment of every file will be the path I am expecting you to put the code at in order for it to work.

Now please stop emailing me about it.

Starting with our First React Component.

Let’s look at our first component, the Profile Component. This one will render a single element of the List part of our application. It renders just enough data to identify it while making us want to click it:

// src/components/Profile.js
import React from 'react';
import PropTypes from 'prop-types';

const propTypes = { 
    onClick: PropTypes.func, 
    selected: PropTypes.bool, 
    label: PropTypes.string,
};

const Profile = ({ onClick, label, selected }) => { 
    //If it's selected, highlight it. 
    const classes = selected ? 'bold' : ''; 
    return ( 
        <li onClick={ onClick } className={classes}> 
            { label } 
        </li> 
    );
};

Profile.propTypes = propTypes;
export default Profile;

So I believe right away we have some questions. Let’s start from the top:

  • The first lines of code are Import statements, which allow us to migrate certain pieces of code from other files onto ours.
    • We bring in React because it has to be a part of every file that we use React Components in.
    • And we also import a library called ‘prop-types’. It allows us to define the variable type for each of the properties in our component.
  • Then we define our Component’s propTypes at the very top inside their own variable. We do this to make it easier to see, at a glance, what properties our component makes use of. This is in favor of creating better user interfaces out of our code.
  • Next we create what is known as a Stateless Functional Component. This means our component will be a function meant to display data, while having very minimal logic.
    • You may notice the syntax on the component we’re building. It’s in the shape of an Arrow Function with De-Structured Arguments. I talk more about them in This Article.
  • You may also notice we’re using a Ternary Operator to set the classes variable based on one of our properties.
  • We assign the propTypes variable to our Component’s propTypes.
  • And, finally, we expose our component to the rest of our application by using the export keyword.

I’d like to take this opportunity to make a point here. The profiles component is made to be intentionally generic. Mainly because it makes the component easier to re-use. Especially since all you would need is to give it a label to have it render.

Displaying our selection with a React Component

Our next component represents the bigger subset of data that we render on clicking a particular list item. The Detail part of our List Detail View:

// src/components/Selection.js
import React from 'react';
import PropTypes from 'prop-types';

const propTypes = {
 user: PropTypes.object
};

const Selection = ({ user }) => ( 
    <ul> 
        <li>Name: { user.name }</li> 
        <li>Username: { user.username }</li> 
        <li>Phone: { user.phone }</li> 
        <li>Email: <a href={`mailto:${user.email}`}>{user.email}</a></li> </ul> ); Selection.propTypes = propTypes; export default Selection;

Once again we keep it generic. Which means this component will work as long as we give it an object, even one that’s empty. Additionally, we could also define the shape of the object in the propTypes using `PropTypes.shape`, which you can read more about here.

However, one thing to note here is the ‘mailto’ href with a Template String.

  • First, Template Strings are an ES6 Feature that will automatically interpolate values in between ‘${}’ characters. Although that is only one of their many graces. You can read more on them here.
  • Second, the ‘mailto’ href will automatically open an email client with an email directed at whatever address comes after the colon. And, of course, it will allow you to modify it.

I hope by now we’re getting the hang of this. Because now that we have our two building blocks, we need a place where to use them.

The react side of our main Class Component.

Our next main component will integrate our previous both, forming a little bit of a Container of sorts. Additionally, we’ll be using this component to interact with our MobX store.

For this reason, the explanation itself will be divided in two parts: A React part and a MobX one. Please don’t try the code out until you have implemented both.

Let’s start with the React one. The App component has to render a list of Profiles, as well as making sure that, on clicking one of them, a Selection is rendered. So let’s handle that:

// src/components/App.js
import React, { Component } from 'react';
// Covered in the MobX Section 
import { observer, PropTypes } from 'mobx-react';
// Covered in the MobX Section 
import _ from 'lodash';

// Relative imports
import Selection from './Selection';
import Profile from './Profile';

const propTypes = {
    store: PropTypes.object
};

// Observers can react (ba dum tss) to changes in observables
const App = observer(
class App extends Component {
    componentWillMount() {
        this.props.store.getUsers();
    }
    renderSelection(){
	if (_.isEmpty(this.props.store.selectedUser)) return null;
	return (
	    <div className='selection'>
		<Selection user={this.props.store.selectedUser}/>
		<button onClick={this.props.store.clearSelectedUser}>
                    Close Profile
                </button>
	    </div>
	);
    }
    renderProfiles(){
	return this.props.store.users.map((user) => (
	    <Profile
		selected = {user.id === this.props.store.selectedId}
                key = {user.id}
		label = {user.name}
                onClick = { () => {this.props.store.selectUser(user)} }
	     />
	));
    }
    render(){
	return (
	    <div>
		<h3>Employee Directory</h3>
		{this.renderSelection()}
		{this.renderProfiles()}
	    </div>
	);
    }
});

App.propTypes = propTypes;
export default App;

And this is where things start to get a little bit interesting. Let’s start with the general parts:

  • Instead of including just React, we’re also importing a class called Component. Which allows us to extend our class off of it and let React know that this is a Component we are creating.
  • We’re also importing Lodash, a JavaScript utility library. Which, amongst other things, excels at manipulating collections (Lists of Objects).
    • We mainly use the _.isEmpty() method of Lodash to decide on whether or not to render Selection.
  • There’s also syntax for Relative Imports. Which means you write the path to the file you want relative to the one you’re writing in. This is how we get access to our previous two components.
  • Next we note that this component is in fact Class based. Meaning we’ll be handling more logic inside it, as well as accessing it’s life-cycle methods.
  • Finally, note that we are using Helper Rendering Functions to handle the more complicated aspects of our logic. This leaves the actual render method to be as concise as possible.
    • The renderSelection method decides on when to render the Selection, as well as a button to close it.
    • And the renderProfiles method takes care of rendering a list of Profiles based on the users array of our store.

The MobX Side of our application.

From this point out I will do my best to explain everything, every step of the way. Especially since that is what we’re here for in the first place. So, let’s start looking at our App.js file again:

The MobX side of our main Class Component.

// src/components/App.js
// ...
import { observer, PropTypes } from 'mobx-react';
// ...

This is the first line of notice, importing observer from a library known as mobx-react. As we discussed in the previous post, MobX is a State management solution, and not directly linked to React. For such reasons mobx-react represents a set of bindings to allow both MobX and React to work together.

The method observer in particular is used to modify an existing component so that it responds to changes in a MobX Store. But we’ll get into what a Store is soon enough.

Additionally, I wanted to point out that MobX has its own set of PropTypes. They contain everything regarding observables and are extremely useful when combining MobX with React.

// ...
const App = observer( class App extends PureComponent {
// ...

A little note here, we are using the observer method as a function.  The method in itself is better known for it’s use as a decorator, which means using a function that adds functionality onto our class without directly modifying it. They’re part of proposed ES6+ syntax but not yet an official part of the language. You can find more information on decorators here.

However, we’re not currently using decorators because they’re not supported by the current version of create-react-app. So, since it’s the friendliest way to start coding react, and in order to keep this as close to real world as possible, we won’t use them either.

//...
componentWillMount() { this.props.store.getUsers(); }
//...

Finally, here we can see an example of how the store will be used throughout this component.

It gets passed in as props and we use different methods (called actions) to interact with it. Additionally, we’re using our store here inside a life-cycle method, particularly componentWillMount, which will get called right before our component’s first render.

And we can see examples of the store getting used in different parts of the app:

  • <button onClick={this.props.store.clearSelectedUser}> as an on click handler.
  • <Profile selected = {user.id === this.props.store.selectedId} // ... as a condition for being selected.
  • And even renderProfiles(){ return this.props.store.users.map((user) => ( // ... as an array to map over.

But now we’ve teased enough about the store, time to actually build it.

Our MobX Store.

A store represents a subset of application data. You can have multiple stores (as Flux suggests) or encompass everything into a single one (which is the way that Redux handles it.)

However, the main idea still stands that the store should represent the single source of truth for a particular subset of application data. That way we can make sure our application is always reacting to the right information.

With that being said, let’s look at our store:

// src/models/UserStore.js
import {
    observable,
    action,
    computed,
    decorate
} from 'mobx';
import axios from 'axios';

class UserStore {
     // First we place all of our observers
    users = [];
    selectedUser = {};
    get selectedId() {
        return this.selectedUser.id;
    }
    // In strict mode, only actions can modify mobx state
    setUsers = (users) => {
        this.users = [...users];
    }
    selectUser = (user) => {
        this.selectedUser = user;
    }
    // Managing how we clear our observable state
    clearSelectedUser = () => {
        this.selectedUser = {};
    }
    // An example that's a little more complex
    getUsers() {
        //Managing Async tasks like ajax calls with Mobx actions
        axios.get('http://jsonplaceholder.typicode.com/users').then(response => {
            this.setUsers(response.data);
        });
    }
}

decorate(UserStore, {
    users: observable,
    selectedUser: observable,
    selectedId: computed,
    setUsers: action,
    selectUser: action,
    clearSelectedUser: action,
    getUsers: action
});

const store = new UserStore();

export default store;
export {
    UserStore
};

And, just like with our component, we’ll be breaking things down little by little here:

// ...
import { observable, action, computed, decorate } from 'mobx'; 
import axios from 'axios'; 
// ...

First we’ll be importing the main building blocks of our application:

  • MobX observables. Pieces of state that cause reactions and computations in response to their own changes.
  • Actions. Nothing too special about them, they’re just functions that are allowed to modify state in Strict Mode.
  • Computed observables. Pieces of state that are derived from Observables.
  • And decorate, a method which we’ll use to allow us to use create-react-app. Just kidding, it works by modifying methods and properties to be the appropriate one out of the three above types of data.

If you’re wondering about Reactions, don’t be. Just like I explained in my previous post, React is taking care of most side effects that result from altering our observable data. React is handling our reactions.

The other thing to note here is that we’re importing Axios, a very popular library for handling Ajax calls.

//... 
class UserStore { 
    users = [];  
    selectedUser = {}; 
// ...

Two things of note here:

  • Our MobX Store is going to be a regular ES6 Class, which have similar syntax to what most of us are used to. This plays into the familiarity topic that I was talking about earlier. Because it is a regular old class, it gives us a little bit of certainty and lets us stand on familiar ground.
  • The Observables themselves are just properties of the class. We’ll mark them as observable with decorate later.
  • This means that you can, in fact, have properties in your store that are not observable. Just don’t include them in the decorate function.
// ...
get selectedId() { return this.selectedUser.id; }
// ...

Computed values are represented as getter functions in our Store Class. Which means that they will become available afterwards as properties (without having to call the function.)

For example, selectedId in: <Profile selected = {user.id === this.props.store.selectedId} />.

//...
setUsers = (users) => { this.users = [...users]; } 
selectUser = (user) => { this.selectedUser = user; }
// Managing how we clear our observable state
clearSelectedUser = () => { this.selectedUser = {}; }
// An example that's a little more complex 
getUsers() {
    axios.get('http://jsonplaceholder.typicode.com/users').then( response => { 
        this.setUsers(response.data); 
    }); 
// ...

Over here we’re finally dealing with actions. Simply put, we’ll be directly mutating state using them. Some simple examples include just setting data. While more complicated ones like getUsers can even use external libraries to fetch said data.

Additionally, it is important to note that we created clearSelectedUser so we could be the ones to decide how we’re going to be resetting our own state. It brings a little bit more control.

It’s also worth noting that we’re declaring all our actions as Arrow Functions. This allows the context of This to be bound automatically. And that way we don’t have to worry about losing it

// ...
decorate(UserStore, {
    users: observable,
    selectedUser: observable,
    selectedId: computed,
    setUsers: action,
    selectUser: action,
    clearSelectedUser: action,
    getUsers: action
});
// ...

And now we get into the decorate function. You’ll simply be creating an object with the properties, getters and functions you wish to keep track off. And then you can just assign the proper values:

Rule of thumb:

  • Properties become observables.
  • Methods become actions.
  • Getter functions become Computed Observables

And of course, you can exclude anything that you don’t wish to track from this function.

// ...
const store = new UserStore(); 
export default store; 
export { UserStore };
// ...

Finally, we export both our store and an instance of it.

Note: There are different approaches as to how we handle our stores. One suggest exporting an instance of said store and using it as a singleton. The other suggests having different stores, each with its own subset of data. It’s really up to your preference and the situation.

Putting it all together.

As it stands we only have to render our App component to an Html Node and pass it our store. So we’ll be doing just that:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
// Relative imports
import App from './components/app';
import store from './models/UserStore';

ReactDOM.render(<App store={store} />, document.querySelector('.container'));

As this file isn’t really all that big, I’ll do all of my explaining right here.

  • We import ReactDOM, which allows us to actually render our application to an html element in our page by means of ReactDOM.render().
  • We also import React, as well as both our main component (App.js) and an instance of our store.
  • Finally, as we’re rendering our main component, we pass in the store instance to it as a prop.

And that’s basically it. This way our store will get passed down to App, which will use it to gather, render and manipulate the data our store contains.

  • The store will get the data it needs when the componentWillMount life-cycle method is triggered.
  • It will then use this data to render each one of our Profiles by mapping over it’s users array.
  • Clicking any of the profiles will cause us to populate the selectedUser state. The Selection component will render with its data as a direct reaction to that.
  • Additionally, having a selectedUser will cause selectedId to compute as a reaction. This will in turn highlight one of our Profiles.
  • Finally, we can click the ‘Close Profile’ button to reset our selectedUser, and bring everything back to the start.

If you have all the starter pack from github, you can switch to the mobx-react-list-detail-view app for the full tutorial code.

Wrapping up our first MobX + React Application.

What we covered here today is a very simple example of what building an application using MobX is like. However, all of the concepts we learned will translate perfectly into a production environment.

I’m also aware that my explanation isn’t perfect. There may be things that seem obvious to me but probably aren’t. But if anything, I’m hopeful that I at least managed to break the ice and show you that MobX isn’t really all that complicated. And that mixing it with React isn’t rocket surgery.

There are many topics that are still left to be covered: Testing, Linting, Strategies for scaling with MobX; and many more. Most of which I think I’ll be leaving in the hands of more experienced people.

And, if this picked your interest, the main MobX page has a very comprehensible list of resources you can use to keep learning.


Hopefully this updated version helps all of you that were trying to use my very outdated code from 3 years ago.
Good luck and happy learning!

Author

My name’s Orlando Paredes Hamsho. I’m a 25-year-old Web Developer living (mostly) in Guadalajara, Mexico; albeit I intend to move pretty soon. Apparently, I also run a blog now, and have been doing so for a while.

Write A Comment