I have been writing applications using React and Redux for quite some time now and thought of trying other state management solutions out there. It's not that I have faced any issues with Redux; however, I wanted to explore other approaches to state management. I recently came across MobX and thought of giving it a try. The library uses the premise of `Observables` to tie the application state with the view layer (React). It's also an implementation of the Flux pattern wherein it uses multiple stores to save the application state; each store referring to a particular entity. Redux, on the other hand, uses a single store with top-level state variables referring to various entities.
On Redux vs MobX:
The above React component class has the `@observer` decorator, declaring that view component will get updated with the changes in the props. Notice `this.props.cart` in the component's render function, the observable property declared in the Cart class - itemsInBag is being observed by this React component. Any additions/deletions/updates in the observable property will rerender the observer component.
Here the view component gets an instance of the cart object as a prop from the parent container:
const cart = new Cart()
return (
<ShoppingCart cart={cart} />
)
The view component also has `this.props.cart.bagCount` instead of `this.props.cart.itemInBag.length`; this is on purpose. MobX provides decorators that can derive values from the state. In this case, the bagCount property is declared as a `@computed` property:
class Cart {
@observable itemsInBag = [];
@observable itemsInWishlist = [];
@computed get bagCount() {
return this.itesmsInBag.reduce((count, bagItem) => {
return count + bagItem.count;
}, 0);
}
}
On Redux vs MobX:
Redux uses functional programming approach to update the state:
(state, action) => newState;
It accepts two arguments - current state and an action and returns a new state. In Redux, the state is immutable i.e. to update the view, the state reference in the Redux store itself should be updated. For example, to add a new item to the cart you would:
(state, action) => {
return [...state.items, action.item];
}
instead of
return state.items.push(item);
MobX, on the other hand, uses an object-oriented approach to state management. A store in MobX is nothing but an instance of a class which has observable properties:
class Cart {
@observable itemsInBag = [];
@observable itemsInWishlist = [];
}
The decorator `@observable` (defined in `mobx` library) is used to declare a property in the class as an observable. It's initialized to an empty array and any changes to this array will let the observers know. In this case, the observer is our view component in React:
(state, action) => newState;
It accepts two arguments - current state and an action and returns a new state. In Redux, the state is immutable i.e. to update the view, the state reference in the Redux store itself should be updated. For example, to add a new item to the cart you would:
(state, action) => {
return [...state.items, action.item];
}
instead of
return state.items.push(item);
MobX, on the other hand, uses an object-oriented approach to state management. A store in MobX is nothing but an instance of a class which has observable properties:
class Cart {
@observable itemsInBag = [];
@observable itemsInWishlist = [];
}
The decorator `@observable` (defined in `mobx` library) is used to declare a property in the class as an observable. It's initialized to an empty array and any changes to this array will let the observers know. In this case, the observer is our view component in React:
Here the view component gets an instance of the cart object as a prop from the parent container:
const cart = new Cart()
return (
<ShoppingCart cart={cart} />
)
The view component also has `this.props.cart.bagCount` instead of `this.props.cart.itemInBag.length`; this is on purpose. MobX provides decorators that can derive values from the state. In this case, the bagCount property is declared as a `@computed` property:
class Cart {
@observable itemsInBag = [];
@observable itemsInWishlist = [];
@computed get bagCount() {
return this.itesmsInBag.reduce((count, bagItem) => {
return count + bagItem.count;
}, 0);
}
}
The computed getter (get bagCount) function returns the number of items in the cart; it's executed every time the observable properties in the class are updated. This is very handy because the computation logic is present in the Cart class instead of it being in the View component. It means consistency in the model layer and the code remains DRY.
On setting up the environment for using ES7 decorators
I generally use `create-react-app` to quickly scaffold out the front-end and then modify it to add other libraries. However, it reported errors when I started to use the decorators (observable, observer and computed). Later I discovered that ES7 decorators are still at the proposal stage and are not part of the language yet and thus it's not supported in `create-react-app`. I had to use `yarn eject` to get all the configuration in my local setup and then add babel plugins to get the decorators working:
yarn add --dev babel-cli babel-preset-env babel-plugin-transform-decorators-legacy
and then added .babelrc file to the project's root directory:
{
"presets": ["env", "react", "stage-1"],
"plugins": ["transform-decorators-legacy"]
}
This should not report any errors when you start the app (using yarn start). Also, there's an option to use the function `extendObservable` to create an observable instead of using ES7 decorators:
extendObservable(this, {
itemsInBag: [],
get bagCount() {
return this.itemsInBag.length;
}
})
This is very much ES5 syntax; however, I really liked the ES7 decorators' syntax and I hope it gets accepted in the language.
Comments
Post a Comment