So I started this week with the primary goal of re-writing my stores and migrating them to use Immutable collections. In the last week’s post, I laid out the motivation on why this is a good idea and how it will speed up the React application.

While the benefits were clear, I was still not sure on how I could implement and exploit the collections provided by Immutable to my advantage. After numerous posts on forums, Github issues and IRC discussions I realized that I was looking at it from the wrong direction.

The key idea was explained to by Matt Greer whose extremely long and clear email helped me really understand the issue. With that out of the way, slowly and steadily I was able to migrate the store piece by piece to Immutable.

Initially it was a bit frustrating to keep updating nested structures using set but after finding the updateIn method, things got really good.

// on question dropped
var index = this.getBlockIndex(questionObj.parentID);
var newSurvey = this.data.surveyData.updateIn([index, 'questions'], list =>
    list.push(newQuestion)
);
this.updateSurveyData(newSurvey, true);

Throughout the store, I also keep track of two maps namely, _optionMap and _questionMap which are basically mappings from the option ID to the question ID and from the question ID to the block ID respectively.

// on question dropped - update question map with new question
var block = this.data.surveyData.get(index);
_questionMap = _questionMap.set(newQuestion.get('id'), block.get('id'));

Adding Undo

After finishing up integrating Immutable and adding PureRenderMixin in all my components, my next goal was to quickly add a undo feature that would allow users to undo a destructive action.

The idea here is simple - at each stage cache the application state in a history object and use that to go back when the user clicks on undo

// mananging history
var _history = [];

// in the store
updateSurveyData(data, cache = false) {
    if (cache) {
        _history.push({
            data        : this.data.surveyData,
            optionMap   : _optionMap.toJS(),
            questionMap : _questionMap.toJS()
        });
    }
    this.data.surveyData = data
    this.trigger(this.data);
},

The other side benefit that this allowed me was to add an undo feature which made sure users are allowed to go back any destructive action they carry out in the UI. To make sure only destructive actions are cached, the updateSurveyData takes an extra cache param which when true pushes the data into the history. After this, the undo operation is simple to implement.

onUndoSurvey() {
    // retrieve cached data
    var { data, optionMap, questionMap } = _history.pop();
    _questionMap = Immutable.Map(questionMap);
    _optionMap = Immutable.Map(optionMap);
    this.updateSurveyData(data);
}

That finally sums up the work that went in this week to finish two key features. With the architecture now stable, I feel much more confident in adding more features without impacting the performance. However, now that the codebase has grown to roughly ~1500 lines of code, it is high time to add a test suite so that refactoring becomes more managable and less scary.

The plan for this week, then, is to add tests and make the codebase more robust. Till next time.