It’s 2017, time to give Redux another thought

I love Redux. It has been, so far the most successful and popular state management library for the modern JavaScript community. Hands down! A million download per month is an indication of it’s remarkable acceptance. It makes application easier to test, provides predictable deterministic state, makes application easy to debug, helps you scale well to large teams out-of-the-box and finally and most importantly it is opinionated and well controlled, which is good when you work with a large team.

As they say in their official page:

It (Redux) helps you write applications that behave consistently, run in different environments (client, server, and native), and are easy to test. On top of that, it provides a great developer experience”.

Every bit of that is true. But, as with any library Redux is not without it’s problems. Redux slows you down because you have to follow a pattern even for trivial things. More often than not, you’d write a lot of “boilerplate” code to make a simple features work. That’s the pain part of it and that makes redux complicated.

A word about Mobx:

Mobx, an younger sibling to Redux, hoped to solve this exact pain point. Mobx gives developers the power to code faster as there is more auto-magic and thus code less “boilerplate”. It gives you that freedom that Redux didn’t, thus making it the most flexible and fun library to work with.

This great power is it’s greatest weakness. Too much freedom often leads to building something that deviates from the original goal. As the team scales, it becomes highly difficult to organise and maintain your code. Often you are at the mercy of the genius who started the project and maintains it.

This made me think. What if we can find a sweet-spot between these two libraries? With all the great tools that JavaScript community has, can we find a better way that would help us enjoy all the benefits of Redux in Mobx way? Let’s find out.

Both Redux and Mobx are often seen associated with React, though it could be independently used with any other framework. So in this article I would try to do the same; find a way to make Redux work better, while using it with React.

What changed in 2017?

Some of the great tools and concepts in our hands (and their wide spread adoption) in 2017 helped me take this venture. It’s not fair if I don’t leave a note about these tools. However if you are like me and want to see the idea in action first, skip this section and come back later.

Classes

Many libraries were born at a time where there was no convenient way to create classes in JavaScript. So they evolved around non-class concepts. But now, many modern libraries including Angular, React, MobX, use Class as the preferred way of doing things.

Facebook in their recent post says: “JavaScript classes are now the preferred way to create components in React.”

Classes are the new normal for JavaScript. I have been a Java developer for many years. Therefore I didn’t have a problem welcoming Classes to JavaScript. However people coming from more non-structured languages may find it difficult to embrace this concept. I was very careful to keep it to bare minimum in this journey.

Decorators

Decorators seek to enable annotating and modifying JavaScript classes, properties and object literals at design time while keeping a syntax that’s declarative. More over it makes your code cleaner.

Though Decorators are available as an experimental feature of TypeScript, Mobx and Angular team has extensively used it in their APIs.

I don’t see it going away soon. Therefore I wanted to use decorators to take away the painful “boilerplate” code from the developers.

TypeScript

Lack of standard library, structure and types makes JavaScript very agile and tool of choice for programmers when hacking a new idea. No need to spend much time thinking about the details of the language. But working on a large project is different. You need strong structure, standards and type checks to assure the quality of the project in the long run. TypeScript brings flexibility of the JavaScript while providing optional standards and typings. Besides, TypeScript is extremely well-tested and widely used.

Popular teams behind Angular, RxJS, Ionic, Cycle.js, Blueprint, Dojo, NativeScript, Plottable and many more, use TypeScript

So I decided to take this path with TypeScript.

Observables

Reactive programming needs a strong reactive foundation and Observable are the best fit. The most important part is: Observables are lazy. That means, unless there is a need to do so, Observables will never execute a portion of code. Observables are at the core of Angular and Mobx. Though it is not visible outside, this approach uses Observables at it’s heart so that the code is optimised and scalable.

To summarise Classes, Decorators, TypeScript and Observables are the pillars of my approach.

The approach

Install StateX library to start with

npm install statex --save

Redux asks you to create:

  • Application State — describe application state as plain objects and arrays.
  • Actions — describe changes in the system as plain objects.
  • Reducer Functions — describe the logic for handling changes as pure functions.
  • Consume Data — using connect function
  • Dispatch Action — using a dispatcher

Application State

We have had problems with plain objects and arrays especially when used with large projects, that’s why we need type checking. Since we use TypeScript, define your application state using Interfaces. You get to use plain objects and array as suggested by the Redux team, while they are validated for integrity at development time.

interface Todo {
id: string
text: string
completed: boolean
}
interface AppState {
todos: Todo[]
}

Actions

In Redux, actions are described as plain objects and identified by a string— name. But how do we maintain the integrity if there is no type check? How do make sure we don’t miss-spell an action? And finally how can we make sure that the required payload is passed and is in proper format?

To answer all these questions, I decided to define actions as classes with the necessary arguments passed on to the constructor. Never again we will miss-spell an action, miss a required parameter or pass a wrong parameter. Extending the action from Action class, makes it listenable and dispatch-able.

import { Action } from 'statex'
export class AddTodoAction extends Action {
constructor(public todo: Todo) { super() }
}
export class RemoveTodoAction extends Action {
constructor(public id: string) { super() }
}

Reducer Functions

The following code pretty much summarises, what a reducer functions is. They are pure functions modifying the state of an application in a controlled manner. Given a state and an action it produces a newState.

const newState = reducer(oldState, action)

A reducer function for the familiar Todo is usually written as:

const todos = (state = { todos: [] }, action) => {
switch (action.type) {
case 'ADD_TODO':
return {
todos: state.todos.concat(action.todo)
}
case 'REMOVE_TODO':
return {
todos: state.todos.filter(
todo => todo.id != action.id
)
}
default:
return state
}
}

To start with, I didn’t like how switch cases are used for managing different actions. We can do better than that. What if we could split that into multiple pure functions, each handling an action, and somehow connect actions to each function (this I will explain later). I like the clean look of it.

const addTodo = (state, action) => {
return {
todos: (state.todos || []).concat(action.todo)
}
}
const removeTodo = (state, action) => {
return {
todos: (state.todos || []).filter(
todo => todo.id != action.id
)
}
}

It doesn’t hurt if you wrap it in a class. You will know why we have to do this later. You still get to maintain the reducer functions as pure functions.

class TodoStore {
  addTodo(state, action) {
return {
todos: (state.todos || []).concat(action.todo)
}
}
  removeTodo(state, action) {
return {
todos: (state.todos || []).filter(
todo => todo.id != action.id
)
}
}
}

It’s time to bring the power of decorators to light. Annotate TodoStore with @store and each reducer functions with @action. Take a note that I have also added type to each parameters and return type.

While @action uses the second parameter to the reducer function to bind the correct action, @store make sure that the store is indeed instantiated at the startup.
@store
class TodoStore {
  @action()
addTodo(state: AppState, action: AddTodoAction): AppState {
return {
todos: (state.todos || []).concat(action.todo)
}
}
  @action()
removeTodo(state: AppState, action: RemoveTodoAction): AppState {
return {
todos: (state.todos || []).filter(
todo => todo.id != action.id
)
}
}
}

Consume Data

Now let’s look at the part where data is consumed. A typical container component using redux, would look like this:

export class TodoListComponent extends React.Component {

render() {
const todos = this.props.todos.map(
todo => <li key={todo.id}>{todo.text}</li>
)

return <ul> {todos} </ul>
}
}
TodoListComponent.propTypes = {
todos: PropTypes.array.isRequired
}
const mapStateToProps = state => ({
todos: state.todos
})
export default connect(
mapStateToProps
)(TodoListComponent)

In my approach I’d like to use @data decorator and a selector function (a function similar to mapStateToProps) to get updates from application state. The property gets updated only when the value returned by the selector function changes from the previous state to the current state.

Now that becomes:

import * as React from 'react'
import { data, inject } from 'statex/react'

class Props {
@data((state: AppState) => state.todos)
todos: Todo[]
}

@inject(Props)
export class TodoListComponent extends React.Component<Props, {}> {

render() {
const todos = this.props.todos.map(
todo => <li key={todo.id}>{todo.text}</li>
)

return <ul> {todos} </ul>
}
}

This code is easy to understand while TypeScript takes care of the integrity. Isn’t it?

Dispatch Action

No singleton dispatcher! Instead let every action act as dispatcher by itself. One less dependency to define, inject and maintain.

new RemoveTodoAction(id).dispatch();

To Summarise

As you can see I’ve used every bit of Redux as it is. All I did was to reduce the code that glue things together, while providing better readability and type integrity. If you would like to know how asynchronous tasks can be managed please checkout https://github.com/rintoj/statex/tree/master/examples/todo-react-ts#reducer-functions--async-tasks

Here is all of what we spoke about, put together:

A Bonus Point

StateX ensures that the state is indeed immutable. This will allow you to take advantage of React’s shallow change detection strategy — which is far performant. To make use of this, we must define all components as PureComponent. Read more about it here.

Since application state is immutable, the reducer functions will not be able to update state directly; any attempt to update the state will result in error. Therefore a reducer function should either return a portion of the state that needs change (recommended) or a new application state wrapped in ReplaceableState, instead. And the magic happens behind the scene.

Want to try?

Checkout my repository https://github.com/rintoj/statex/tree/master/examples/todo-react-ts.

This library is available for Angular and non-typescript version of React as well. Check https://github.com/rintoj/statex/tree/master/examples

To know more about how to use StateX with Angular, checkout this article.

A final note

By any means this is not an attempt to dishonour Redux or MobX and the people behind these libraries; they are awesome. I’m just trying to find a sweet-spot between these two libraries with the modern tools available in 2017, so that developer’s can enjoy the power of Redux in Mobx way.

Please let me know what you think. I’m eager to hear from you.

Hope this article was helpful to you. Please make sure to checkout my other projects and articles. Enjoy coding!

Show your support

Clapping shows how much you appreciated Rinto Jose’s story.