Managing React Application State with Mobx — Full stack tutorial (Part 1)

Vladimir Kopychev
Level Up Coding
Published in
10 min readMay 14, 2018

--

A fullstack tutorial of an SPA with React JS + MobX on the frontend and Django-rest-framework on the backend, protected by token-based authentication.

Introduction

React is one of the most popular UI libraries used by frontend developers in all different kinds of applications. The advantages of React are covered in this article as well as in various blogs across the web. In my previous articles, I tried to focus not on React itself, but on some powerful tools for state management, data flow implementation, routing, action dispatching etc. I started with quite simple and not really well-known Reflux library and worked my way to the well-known and powerful Redux in combination with Redux-Thunk and React-Router-Redux.

In this article I will focus on the well-known MobX library, designed for scalable state-management. In the first part we’ll start writing single-page application (SPA) with React + Mobx on the client side and the Django-rest-framework (with Django version 1.11) for backend, a framework popular with Python developers. This application will be responsible for Users and Todos list, the same as in previous articles and will be protected with some authentication. In this article we will focus on the following parts:

  • Registration of a new user
  • Authentication using tokens
  • Displaying the list of the users
  • Performing a search across the list

In following parts, we will continue with the implementation of todos. You can find working code example in the repository .

Important WARNING: Registration and authentication used in this article is not guaranteed to be 100% secure and production ready although it uses quite safe workflow storing session in database. Also this tutorial is not production-ready tested, because its purpose is to show a fullstack tutorial for django + MobX + React JS with examples of the main MobX features.

Server-side application

To get the server-side working on your computer simply run

pip install -r requirements.txt

To install all python dependencies run the follow

python manage.py runserver

(You are free to skip some of them except mandatory ones like django and django-rest)

The source code of the server-side python application can be found here. There’s nothing special in it — it’s simply a django-rest backend application with sqlite sample database.

For authentication we will use django-rest-auth. It’s more secure relative to using JWT for user sessions because it stores the session token on server-side ID database and provides you ability to validate the token which the server receives from client-side. To enable this library for the user authentication, we need to include it in the installed apps list and define urls. We will define different URL paths for API calls and for authentication in our main urls.py

url(r'^api/', include('todos.urls')),
url(r'^rest-auth/', include('rest_auth.urls')),

We will use the default login view from the rest_auth library and write our simple view for the user registration

class Registration(generics.GenericAPIView):
serializer_class = UserRegistrationSerializer
permission_classes = (AllowAny,)
def post(self, request, *args, **kwargs):
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = self.perform_create(serializer, request.data)
return Response({}, status=status.HTTP_201_CREATED)def perform_create(self, serializer, data):
user = serializer.create(data)
return user

In this article I will not focus on the backend part, because it’s pretty basic django application with a simple sqlite database. We will add some functionality to extend default user model manager (for example — search method to filter users with search form post parameters)

def search(self, **kwargs):
qs = self.get_queryset()
if kwargs.get('first_name', ''):
qs = qs.filter(
first_name__icontains=kwargs['first_name'])
if kwargs.get('last_name', ''):
qs = qs.filter(
last_name__icontains=kwargs['last_name'])
if kwargs.get('department', ''):
qs = qs.filter(department__name=kwargs['department'])
if kwargs.get('country', ''):
qs = qs.filter(country__name=kwargs['country'])

You are free to explore other features in the repository.

Client-side application

The frontend portion of this tutorial is based on the previous one for React and Redux. The source code can be found in the client folder in the repository. It uses standard create-react-app boilerplate with some added packages:

"mobx": "^3.5.1",
"mobx-react": "^4.3.5",

For Mobx support within our React app, we add the following plugins:

"babel-plugin-transform-class-properties": "^6.24.1",
"babel-plugin-transform-decorators-legacy": "^1.3.4"

These add class properties support and for decorator-syntax used in MobX.

If you are still facing any issues with compiling frontend I’d suggest using custom-react-scripts instead of react-scripts. You can do it from the beginning of the project (create-react-app my-app — scripts-version custom-react-scripts) or if you already have your create-react-app boilerplate set up you can follow this thread to remove react-scripts and add custom ones:

npm remove react-scripts
npm i -s custom-react-scripts

Then create .env file in your app folder with the following contents:

REACT_APP_BABEL_STAGE_0=true
REACT_APP_DECORATORS=true

Or if you are just downloading the tutorial you’ll be fine because I update package.json to use custom scripts :)

To run the frontend part, first install the packages using:

npm install

Then start the application with the following command:

npm start

Whichs runs the dev server on your local machine

This application has superuser admin@tutorial.com with password q1w2e3r4. Feel free to create another users with registration functionality, command line, or directly in sqlite DB file.

We will use just good old REST API which serves JSON objects. We need API routes for the frontend, similar to API-routing. Since these URLs are for the API, we put the routes in a separate file.

It’s handy to keep services classes to interact with API endpoints calls, local storage API, etc. Please note that ApiService has been changed to work with REST endpoints. There’s also a new history store which we need in order to be able to perform redirect in functional way from the MobX stores because there’s no package like react-router-redux to achieve this feature out-of-the-box (or if there is one, please let me know :) )

import createHistory from 'history/createBrowserHistory'export default createHistory()

Regarding the folder structure, we will keep the same as it was in Redux tutorial. We will have services folder to keep services described above

We will split components into containers — to keep ‘smart’ components connected to the data stores with an ability to trigger actions, forms — to keep components related to the user input data, and, finally, components — to keep stateless presentational components which can be copied over from the redux tutorial or reused in any of the future examples.

Then let’s focus on the MobX features used in this tutorial.

MobX features

One of the primary concepts of MobX is the store. Compared to Redux concepts, the store can provide the same functionality that reducers and actions do in Redux. A simple example of the store is UserStore which is responsible for loading the list of users based on search parameters.

Starting from the top — one of the uses is observable (or “@observable ”)

@observable isLoading = true
@observable isFailure = false
@observable users = []

This decorator defines values (which can be JS primitives, references, plain objects, class instances, arrays and maps) that are supposed to be observed by react components. When this value changes, the component will recognize it and update accordingly (similar to mapStateToProps in redux).

The other useful feature is a computed property — the property with the value based on another value — accessible with getter method.

@computed get total() {
return this.users.length
}

This feature might look familiar for Python developers because of the “@property” decorator widely used in Python.

Since MobX doesn’t specify any explicit place for actions like Redux does, you can also define actions in the store with “@action” decorator. Also you can define actions outside the store, in more ‘redux-like’ way — this is perfectly described in a very useful article about migrating from Redux to MobX. You can also define asynchronous actions using async/await syntax sugar introduced in Javascript ES7

@action async getUsers(params) {
try {
const data = await ApiService.search_users(params)
runInAction(() => {
this.isLoading = false
this.users = data.users
if (Object.keys(params).length && data) {
//save successful request
StorageService.setSearchData(params)
}
})
} catch (e) {
runInAction(() => {
this.isLoading = false
this.isFailure = true
this.users = []
})
}
}

Inside this action is a bit of syntax sugar for it to take a block of code and execute it is an anonymous action. As shown in the code example above, it’s very handy for asynchronous actions.

Of course you can have simple synchronous actions to modify a state of your store, and perform some side-effects (i.e. localStorage update)

@action loadStoredData() {
//load form data from local storage
const data = StorageService.getSearchData() || {}
//dispatch an action
this.loadForm(data)
}
@action clearForm() {
StorageService.removeSearchData()
this.searchData = {}
}
@action clearForm() {
StorageService.removeSearchData()
this.searchData = {}
}

If we need to perform a redirect from an action using declarative way, we can use out history service described earlier in this tutorial:

@action async logout() {
await ApiService.logout(StorageService.getToken())
StorageService.removeToken()
runInAction(() => {
this.isAuthenticated = false
this.isFailure = false
this.currentUser = null
this.isLoading = false
history.push('/')
})
}

In order to inject the store with its awesome features we have several options:

  • Pass store as a prop
  • Instantiate store in constructor via this.store = new MyStore()
  • Use inject functional wrapper
  • Use inject decorator syntax

Let’s pick the last one since it’s more ‘redux-looking’ and clean in my opinion.

First , we are able to combine our stores like the following:

const stores = {
authStore,
userStore,
searchStore,
registerStore,
};

Then we use Redux-like Provider pattern to pass combined store to the App

ReactDOM.render(
<Provider { ...stores }>
<App>
<Router history={history}>
<Switch>
<Route exact path="/" component={Home} />
<Route path={routes.login} component={LoginContainer} />
<Route path={routes.sign_up} component={RegisterContainer} />
<Route path={routes.users} component={UserListContainer} />
</Switch>
</Router>
</App>
</Provider>,
document.getElementById('root')
);

Do not forget to define all imports we need as well:

import { Provider } from 'mobx-react'
import { Router, Switch, Route } from 'react-router'
import history from './services/history'
import authStore from './stores/AuthStore'
import userStore from './stores/UserStore'
import searchStore from './stores/SearchStore'
import registerStore from './stores/RegisterStore'

Then we can easily plug the store we need into each component using “@inject” and “@observer” decorators (i.e. see UserListContainer)

@inject('userStore', 'searchStore') @observer
class UserListContainer extends React.Component {

Injected stores are accessible via props in our container, so we can use properties and actions from the store as our ‘shared state’

return <div className="user">
<UserForm data={searchStore.searchData}
submitHandler={this.getUsers}
changeHandler={searchStore.loadForm.bind(searchStore)}
clearHandler={searchStore.clearForm.bind(searchStore)} />
{userStore.users && <UserList users={userStore.users} />}
</div>;

Or in the case of asynchronous actions we can even use them in lifecycle methods

async componentDidMount() {
this.props.searchStore.loadStoredData()
await this.props.userStore.getUsers()
}

In case the of the LoginContainer or RegisterContainer, we can inject only the store we need to check for authenticated users and redirect if necessary:

async componentWillReact() {
if(this.props.authStore.isAuthenticated) {
await this.props.authStore.fetchProfile()
history.push('/')
}
}

It’s a new lifecycle hook, triggered when the component is going to be re-rendered because of changes in the data it observes.

Running a production build

You can fully test this code using webpack and python dev-servers but if you want to run it using ‘production-like’ build you may need these useful tips.

First of all, you need to build fronend by going to client folder and running:

npm run build

Then you need to define proper urls in your url.py file to serve it from django:

url(
r'^$',
csrf_exempt(TemplateView.as_view(template_name='index.html'))
),
url(
r'^(?P<path>.*)/$',
csrf_exempt(TemplateView.as_view(template_name='index.html'))
),

These two patterns forces all paths to serve the index.html template by default.

url(r'^api/', include('todos.urls')),
url(r'^rest-auth/', include('rest_auth.urls')),

The other two define links to our todos API and rest-auth API

The last one need will serve service-worker.js from the fronend build:

url(
r'^service-worker.js', cache_control(max_age=2592000)(
TemplateView.as_view(
template_name="service-worker.js",
content_type='application/javascript',
)
),
name='service-worker.js'
),

Also we need to define paths to templates in static files in your settings

TEMPLATES = [
{
# ......
'DIRS': ['client/build/'],
'APP_DIRS': True,
# ......
},
]
# .....
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.11/howto/static-files/
CORS_ORIGIN_ALLOW_ALL = True
STATIC_URL = '/static/'
STATICFILES_DIRS = [
'client/build/static',
]

After all those preparations we can see our homepage after running

python manage.py runserver

And navigating to localhost:8000 (or different host/port if defined)

My thoughts about MobX

First of all — MobX and React-MobX cannot be considered as a substitute to React-Redux. It’s a bit of a different thing. MobX is nice and a relatively small library for performing a scalable state management. Compared to Redux it gives you only one strongly defined pattern — the Store, but you can use it in a very flexible way.

There are no strict notations regarding actions, reducers, and middlewares, but it doesn’t mean that you can’t define it yourself.

There are not many add-ons and external libraries for MobX compared to Redux (redux-thunk, redux-saga, react-router-redux, etc) and its community is not as large. However, it’s constantly growing and improving.

After working for a while with both MobX and Redux, I can say that MobX is a bit more ‘lightweight’ and easier to learn, but quite powerful in giving you the proper tools for state management. Although it mostly is concerned with the store layer, it does its job very well. Redux is still the more powerful library in my opinion. It provides you a full architectural pattern regarding stores and actions, and, if you go with additional stuff like thunk and react-router, it also provides you middlewares and injects itself into the routing workflow. However, it’s a bit harder to learn in my opinion and you don’t always need it — it might be too over-engineered for relatively small projects. If you are starting with a small app and expecting it to grow — don’t worry, both Redux and MobX are perfectly scalable.

To be continued …

In the following parts to this tutorial, we will cover:

  • Adding Todos store and the functionality to view Todos list
  • Adding functionality to assign tasks to users, change tasks status, etc
  • Adding unit test examples with React and Mobx

--

--

Full Stack Engineer with passion for Frontend and UI solutions, PhD, coding at @udemy