Organising state in react and react-native applications with Redux Box

Anish K.
5 min readJan 13, 2018

--

Redux Box

Since this article has been published, there have been few additions/improvements in the redux-box API. Please refer to the docs for further details : https://github.com/anish000kumar/redux-box

Redux is amazing!

Chances are you have already heard someone saying this. Redux is indeed amazing and quite simple in itself. But, it can be annoyingly difficult to wrap your head around the whole ecosystem that revolves around redux.

If you want to try out ‘hello world’ of a redux powered app, it won’t take much time or effort. But as soon as you start thinking of a large scale app with redux, you will be bombarded with hundreds of resources all stating different structures and ideologies. And even if you have it setup for once, you will have to redo the same manual work, for each new project, that can be actually avoided.

Besides, some of the trivial tasks like getting the data from state or updating the state can require too much of manual work. For example , typically you would create a reducer first, this reducer will then have a case statement to handle your action. Then you would create an action and an actionCreator to fire the action. Then you would need to create a container component which will map state to props and dispatch to props, and then only you would be able to manipulate the state. Personally, I am not a fan of this process, seems too manual and most of the steps are actually repetitive.

Meet Redux Box !

It aims at making the redux setup process a breeze, for any react/react-native app.

Installation

Run this command in your terminal/cmd to install the package:

npm install --save redux-box

The Basics

Redux box emphasizes on dividing the whole application into multiple modules. Each of these modules manages it’s own state. The module is composed of four parts:

  1. state.js (this file specifies the initial state of the module)
  2. mutations.js (this file specifies the function to be run when a specific action is dispatched, it’s same as reducer but clutter-free)
  3. sagas.js ( this is where you write all your sagas / async operations)
  4. index.js — Container (this file exports a container which encloses the whole module and can be used with render props)

Usage

  1. Setting up the files : The directory structure would look something like below. store directory has an index.jsfile and various modules as shown below.
-src
|-store
| |-index.js
| |-userModule
| | |-state.js
| | |-mutations.js
| | |-sagas.js
| | |-index.js
  1. Creating a redux store :

store/index.js

import { module as userModule} from './userModule';
import {module as someOtherModule} from './someOtherModule';
import {createStore} from 'redux-box';export default createStore([
userModule,
someOtherModule
])

Finally you need to wrap your root component in the <Provider> </Provider> which can be imported from redux; And that's all you would need to setup redux and redux-saga for your application in a modular way.

Understanding the Module

1. state.js

It exports an object specifying the initial state for the parent module. Example: userModule/state.js

export default {
name : '',
email : '',
age : '',
orders : []
}

2. mutations.js

It’s a clutter-free version of the typical reducer we use with redux. it exports an object with multiple functions. Each function name matches certain action type. When an action is dispatched from anywhere in the application, the corresponding method is run and it mutates the state accordingly. Each mutation accepts two arguments: a copy of the state of it's module and the action that triggers the mutations. Here's an example : userModule/mutations.js

export default {
SET_USER_NAME (state, action){
state.name = action.data;
}
}

Each mutation receives a copy of the state hence you can directly change the object. Also, you don't need to return the changed state object, redux-box handles that for you behind the scenes.

3. Sagas

Sagas are used to handle the async operations we might need to perform in our application. Usually to trigger an async process, you would need two sagas: Watcher saga and Worker saga. redux-box make this process clutter free as well, by providing you with a method called createSagas. It's optional to use, and you may want to stick to the traditional process of managing sagas, if you need more flexibility. But for most use cases createSagas can extract away quite a bit of noise. Each Worker saga receives the triggering action as the argument: userModule/Sagas.js

import box from 'redux-box';
import {put} from 'redux-saga';
import api from './api'export default box.createSagas({
'GET_ORDERS_LIST.latest' : function* (action){
try{
yield result = api.getOrders(action.data.id)
yield action.resolve('done')
// more about the action.resolve
is covered in the last section (below).
}
catch(err){
action.reject(err)
//...etc
}
}
})

Above code means when GET_ORDERS_LIST action is dispatched anywhere in the app, run the saga mentioned against it. Also, notice the latest modifier alongside the action name. It's equivalent of takeLatest from 'redux-saga'. You can also use every which would produce the same effect as takeEvery from 'redux-saga'.

4. index.js

It’s the heart of a module which binds all the pieces together. It exports two things : a module and a container (which will be used in any component where you need this module). Here’s how it typically goes: userModule/index.js

import box from 'redux-box';import state from './state'
import mutations from './mutations'
import sagas from './sagas'
export const module = {
name : 'user' // it's important to specify a unique name for each module
state,
mutations,
sagas
}
export default box.createContainer(module)

Now the magic!

Once you have structured the module and it’s files. You are ready to “code at speed of thought” with redux-box. Let’s say I need the userModule in my App.js file. Here we go:

App.js

import React, { Component } from 'react';
import UserContainer from './store/userModule'
class App extends Component {
render() {
return (
<UserContainer>
{(user)=>(
<h1> {user.name} </h1>
<button onClick={()=> user.commit('SET_USER_NAME', 'Roy') } > Change name to Roy </button> <button onClick={()=> {
user.dispatch('GET_ORDERS_LIST')
.then( res => alert('orders updated') )
.catch(err => alert(err.messsage) )
}} >
Get Orders from Api
</button>
)}
</UserContainer>
)
}
}
export default App

The above example illustrate the three major jobs of the module-container.

  • Firstly, you can directly access the state through the container as shown above.
  • Secondly, you can call any mutation or saga by using store.commit functions, which accepts two arguments : the action name and the data (payload) . The signature for any action returned by the store.commit is :
{
type : 'ACTION_NAME'
data : //.. data you attach as the second argument
}
  • Thirdly, if you need to do something after your saga is finished, you would use store.dispatch instead of store.commit. it will also trigger the mutations and sagas, associated with the underlying action, but additionally it returns a Promise. The store.dispatch method basically attached resolve and reject keys to the causing action, which you can yield from you sagas, as you could see in the example saga above.

npm: https://www.npmjs.com/package/redux-box

github: https://github.com/anish000kumar/redux-box

If you find the repo useful, please 🌟 star🌟 it and keep building awesome stuff!

--

--