June 22, 2016

React is the new hotness in the JavaScript world and MobX looks to be becoming the new hotness in the React world, so I thought I would check it out and see what all the fuss is about.

MobX in its purest form is a state-management tool, one that is most commonly being used in conjunction with React. There are many other implementations of React state management (Flux, Redux, Reflux, etc) because application state is hard especially managing state as an application grows. Everyone has their own opinion on their preferred method, but as I looked deeper into MobX and the other options, I found MobX to be an extremely easy and intuitive way to manage state for my application. Forget about boilerplate, configuration and event emitters, MobX covers you with it's observed state patterns.

To dive into it further, I built an Easy MobX Example available on GitHub. You can also view the finished demo here. In this article I will show you step-by-step how to build an easy MobX-powered React application. Let's get started.

Let's start by creating a file titled index.html and adding the following:

<!DOCTYPE html> < html > < head > < title > Easy MobX Example </ title > < link rel = " stylesheet " href = " https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css " > </ head > < body > < div id = " root " > </ div > < script src = " /bundle.js " > </ script > </ body > </ html >

All you need is a div with id="root" and the bundle.js file which will store all our application logic. We're also going to add Bootstrap to our application for a better frontend UI. Now let's add some packages!

Create the package.json file and add this to it:

{ "name" : "easy-mobx-example" , "version" : "1.0.0" , "description" : "Easy Example for MobX + React. Includes hot code reloading in development mode and minification in production mode. Also includes a demonstration of how to hook up a CMS API and do database calls and loading states." , "scripts" : { "development" : "NODE_ENV=development node server-dev.js" , "start" : "NODE_ENV=production webpack -p; NODE_ENV=production node server.js" } , "devDependencies" : { "babel-core" : "^6.9.1" , "babel-loader" : "^6.2.4" , "babel-plugin-transform-decorators-legacy" : "^1.3.4" , "babel-preset-es2015" : "^6.9.0" , "babel-preset-react" : "^6.5.0" , "babel-preset-stage-1" : "^6.5.0" , "eslint-plugin-react" : "^5.1.1" , "react-hot-loader" : "^3.0.0-beta.2" , "webpack" : "^1.13.1" , "webpack-dev-server" : "^1.14.1" } , "dependencies" : { "cosmicjs" : "^2.1.2" , "express" : "^4.14.0" , "hogan-express" : "^0.5.2" , "mobx" : "^2.2.2" , "mobx-react" : "^3.3.1" , "mobx-react-devtools" : "^4.2.0" , "react" : "^15.1.0" , "react-bootstrap" : "^0.29.5" , "react-dom" : "^15.1.0" , "shorti" : "^1.1.6" , "slug" : "^0.9.1" } }

Now run the init script in your terminal.



npm init

Basically we're installing:

1. Babel to compile our ES6 and React JavaScript into ES5.

2. React Hot Loader for magic reloading when we edit any code in development.

3. Webpack to bundle everything.

4. Cosmic JS to connect to a CMS API that will serve as a good example of how to handle loading states during database calls.

5. Express for our node server framework.

6. MobX for state management.

7. React for UI components.

8. React Bootstrap for better UI style.

9. Shorti for easy inline styles.

10. Slug to slugify our titles before adding them to Cosmic JS.

Next let's add our server-side code!

Create a new file titled server.js and add the following:

var express = require ( 'express' ) var app = express ( ) var hogan = require ( 'hogan-express' ) app . engine ( 'html' , hogan ) app . set ( 'views' , __dirname + '/' ) app . set ( 'port' , process . env . PORT || 3000 ) app . use ( express . static ( __dirname + '/public' ) ) app . get ( '/' , function ( req , res ) { res . render ( 'index.html' ) } ) console . log ( 'Listening at localhost:' + app . get ( 'port' ) ) app . listen ( app . get ( 'port' ) )

Here we are:

1. Adding the Express node framework.

2. Adding Hogan, my preferred view template engine.

3. The only route for the app which is '/'.

4. Listening on port 3000 on server start.

Now let's add our client-side code!

Create three new files titled index.js , App.js and AppState.js and put them in a directory titled components .

In index.js add the following:



import React from 'react' import { render } from 'react-dom' import { AppContainer } from 'react-hot-loader' import AppState from './AppState' import App from './App' const data = new AppState ( ) render ( < AppContainer > < App data = { data } / > < / AppContainer > , document . getElementById ( 'root' ) ) if ( process . env . NODE_ENV !== 'production' && module . hot ) { module . hot . accept ( './App' , ( ) = > { const NextApp = require ( './App' ) . default render ( < AppContainer > < NextApp data = { data } / > < / AppContainer > , document . getElementById ( 'root' ) ) } ) }

This is the entry point of our application. It's creating the ability for hot reloading as well as providing our lower-level App component with the data from the AppState class.

Now let's add the following to the App.js file:

import React, { Component } from 'react' import { observer } from 'mobx-react' import { FormGroup, FormControl, Input, Button } from 'react-bootstrap' import config from '../config' import slug from 'slug' import S from 'shorti' import DevTools from 'mobx-react-devtools'; @observer export default class App extends Component { handleInputChange(type, e) { this.props.data.form_data[type] = e.target.value } handleSubmit(e) { e.preventDefault() const title = this.props.data.form_data.title const content = this.props.data.form_data.content if (!title) return const post = { slug: slug(title), type_slug: 'posts', title, content } this.props.data.addPost(post); } handleRemoveClick(post) { this.props.data.removePost(post) } render() { const data = this.props.data let posts_area if (this.is_loading) { posts_area = ( <div style={ S('text-center font-30 mt-80 mb-80') }>Loading...</div> ) } let form_area if (data.posts && data.posts.length) { posts_area = data.posts.map(post => { return ( <div style={ S('mb-20 bg-efefef p-20 pt-15 pb-30 br-4') } key={ 'id' + post._id }> <div onClick={ this.handleRemoveClick.bind(this, post) } className="close">×</div> <div style={ S('font-20 mt-10 mb-10') }>{ post.title }</div> <div style={ S('color-666') }>{ post.content }</div> </div> ) }) } let dev_tools if (config.env !== 'production') dev_tools = <DevTools /> return ( <div style={ S('p-20') }> <h1 style={ S('mb-20') }>Easy MobX Example</h1> <div style={ S('mb-20') }> <h3>This example uses:</h3> <div><a href="https://mobxjs.github.io/mobx/" target="_blank">MobX</a> for state management</div> <div><a href="https://cosmicjs.com/?ref=easy-mobx-example" target="_blank">Cosmic JS</a> as the <a href="https://cosmicjs.com?ref=easy-mobx-example-cms-api" target="_blank">CMS API</a></div> <div><a href="https://www.npmjs.com/package/shorti" target="_blank">Shorti</a> for easy inline styling</div> <div><iframe style={ S('border-none mt-10') } frameBorder="0" src="https://ghbtns.com/github-btn.html?user=tonyspiro&repo=easy-mobx-example&type=star&count=true" scrolling="0" width="160px" height="30px"></iframe></div> <h3>Try it out! Add some posts!</h3> </div> <div style={ S('mb-20') }> <form onSubmit={ this.handleSubmit.bind(this) }> <FormGroup bsSize="large"> <FormControl onChange={ this.handleInputChange.bind(this, 'title')} placeholder="Title" type="text" value={ this.props.data.form_data.title } /> </FormGroup> <FormGroup bsSize="large"> <FormControl style={ S('h-100') } onChange={ this.handleInputChange.bind(this, 'content')} placeholder="Content" componentClass="textarea" value={ this.props.data.form_data.content }></FormControl> </FormGroup> <Button bsSize="large" bsStyle="primary" type="submit" className={ data.is_saving ? 'disabled' : '' }>{ data.is_saving ? 'Saving...' : 'Save post' }</Button> </form> </div> { posts_area } { dev_tools } </div> ) } }

You'll notice there's a lot of stuff happening in this file. Starting from render, our app will default to its Loading state if the data has not yet come back from Cosmic JS. If there are posts returned, the posts array will map into it's own returned row with a post.title , post.content and an "x" to remove the post. In the return statement we also have a form to add more posts and a way to generate a saving state when data.is_saving .

Also notice at the top of the component we have a decorator care of MobX that will watch over the component observing any changes in state and passing the new rendered data down. What the heck is that thing you ask? I have no idea, it just works and it's awesome.

You'll also notice that we have some actions handleInputChange which will set our state of our input fields, so we don't have to use refs (which I recommend against whenever possible), handleSubmit to save our post, handleRemoveClick to remove the post. The last two actions use the Cosmic JS NPM package to add and remove the posts to the CMS API. We're also using Shorti to easily add inline styles to our components.

Finally let's add the following to our AppState.js file:

import { observable } from 'mobx' import { getObjects, addObject, deleteObject } from 'cosmicjs' import config from '../config' export default class AppState { @observable posts = [] @observable form_data = {} @observable is_loading = true @observable is_saving = false addPost(object) { this.is_saving = true; addObject({ bucket: config.cosmicjs.bucket }, object, (err, res) => { this.is_saving = false this.posts.unshift(res.object) this.form_data = { title: '', content: '' } }) } removePost(post) { deleteObject({ bucket: config.cosmicjs.bucket }, { slug: post.slug }, (err, res) => { this.posts = this.posts.filter(apost => { return apost._id !== post._id }) }) } constructor() { getObjects({ bucket: config.cosmicjs.bucket }, (err, res) => { if (res.objects.type.posts) { this.posts = res.objects.type.posts this.is_loading = false } }) } }

Here we tell the MobX observable decorators which state values may possibly change. For our app this includes the posts, the form data, loading and saving states. We also have created a couple functions to add and remove posts from our application. The constructor fires the initial call to Cosmic JS to get our posts.

How great is this?! Notice what we didn't have to add to our app to handle state? We didn't create several files of boilerplate or event emitters, just simple decorators in key parts of the app to observe state changes!

Now let's start up our app! In your terminal run the following:



npm start

Navigate to http://localhost:3000 and you should see your Easy MobX Example app in all its glory! I hope you enjoyed this post as much as I enjoyed building the example app. Follow me on twitter and reach out with any questions below. Thanks!