Building iWisdom: Code refactor

Although the iWisdom application is on the verge of completion, there are many code changes that I wanted to make.

The way I have written this application, particularly, the front-end part is like this: the application consists of a hierarchy of components that make the complete application. I have put each of these components in separate files. Each component, except the top level App component, has just the UI for that component along with the props that decide how that UI is rendered. So, all of these components just have the render method, showing the UI of the component.

This looks beautiful, except in case of App component. As all the application state is present in top level App component, its code has become quite lengthy. This is because it not only has the state of the application but also has all the events taking place in the application. Although they are implemented as separate functions, I still wanted to make it more concise. Also, I want to make the code declarative and functional, so that just by looking at the code, somebody can understand what it is doing. This makes it easy to manage and to add new features.

I have learned some ideas about making the code functional from Redux, which is a popular framework for managing state. I have not used it in my application, but I wanted to implement ideas that it shares. Another thing is that, I want to use RxJS to manage asynchronous events in my application. It not only makes it easy to manage asynchronous events but also opens the opportunity for more enhancements related to async events. In the end, the code I wrote looked much more beautiful, with much fewer lines of code and was much more easy to understand and maintain.

In this post, I am writing about how I refactored my code to use these ideas and make it functional and reactive.

First of all, there was a lot of code in App.js that was related to managing state. Taking the ideas from Redux, I want to move it to a function called reducer, which takes two parameters- state and action and returns the new state. An action is an object with two parameters-type and payload. The type is what action was performed and the payload is any data that is required by reducer to return a new state. Reducer is a pure function that always gives same output for same inputs.

So, I created a new file called reducer.js
vi reducer.js

Then I added a function called reducer to it as below:

export const reducer = function (state, action) {
  switch (action.type) {
    case 'openhome':
    
    case 'openbrose':

    defualt:
      return state;
  }
}

Now, I moved the code in App.js that was in functions like openHome(), openBrowse(), textChange(), etc. into the corresponding actions, so that they will return a new state based on the input state and action.payload.

case 'search':
	return {searchStr: action.payload, screen: action.payload === '' ? 'main' : 'browse'}
case 'openadd':
	var addWisdom = {key: '', title: '', description: ''};
	return {addWisdom: addWisdom, screen: 'add', lookups: []};
case 'openedit':
	var addWisdom = state.wisdom.filter((item) => {
		return item.key === action.payload;
	})[0];
	return  {addWisdom: addWisdom, screen: 'edit', lookups: []};

I removed these functions, except the once with database calls. For those functions, I added action types called ‘gotWisdom’, ‘addedWisdom’ and moved the state based code in the reducer.

Now, that my reducer is ready and has all the code for state management in my app. I just need to emit actions, and then reducer will calculate the new state based on these actions.

I will be using Rx for this. As actions are happening asynchronously, I want to create an observable with a stream of actions. Then I will subscribe to this stream and in callback use reducer to create a new state.

To do this, first install Rx into the application:
npm install --save rxjs-es

Once it is installed, import it in App.js:

import Rx from 'rxjs/Rx';

Also import reducer in App.js, so that I can use it:

import { reducer } from './reducer';

Now, in the constructor, I created a new observable called ‘actionStream’ which will have all the actions taking place in the application. Then I subscribe to it and manage the state.

this.actionStream = new Rx.Subject();
this.actionStream.subscribe((action) => {
  var newState = reducer(this.state, action);
  this.setState(newState);
}

These two lines do all the magic of updating state based on actions happening in the application. However, ‘actionStream’ is empty right now, so we need to push actions in it. They will be pushed when an event happens. For example: in the render method of App.js: push actions to this stream when an event happens:

openEdit={(key) => this.actionStream.next({type: 'openedit', payload: key})}

Do this for all the events in the render method. For database/API calls, I did this in the code for callback of API call:

addWisdomService(addWisdom, res => {
  var key = res.insertedIds[0];
  this.actionStream.next({type: 'wisdomAdded', payload: key});
}, err => {
  console.log(err);
});

This way, I am able to remove all the code that was related to state management from App.js. All it has is render method, and methods to make API calls. The state is managed by putting actions into the ‘actionStream’ observable.

This makes the code a lot manageable and understandable. I can enhance it very easily, as I have complete control on how the state is updating. It also gives me the ability to perform Rx operations on my async events like delay, throttle, etc., which I can do in future.