How to use Immutable.js with redux?

Javascriptimmutable.jsRedux

Javascript Problem Overview


Redux framework is using reducers to change app state in response to an action.

The key requirement is that a reducer cannot modify an existing state object; it must produce a new object.

Bad Example:

import {
    ACTIVATE_LOCATION
} from './actions';

export let ui = (state = [], action) => {
    switch (action.type) {
        case ACTIVATE_LOCATION:
            state.activeLocationId = action.id;
            break;
    }

    return state;
};

Good Example:

import {
    ACTIVATE_LOCATION
} from './actions';

export let ui = (state = [], action) => {
    switch (action.type) {
        case ACTIVATE_LOCATION:
            state = Object.assign({}, state, {
                activeLocationId: action.id
            });
            break;
    }

    return state;
};

This is a good use case for Immutable.js.

Javascript Solutions


Solution 1 - Javascript

Taking your good example with Immutable

import {
    ACTIVATE_LOCATION
} from './actions';

import { Map } from 'immutable';

const initialState = Map({})

export let ui = (state = initialState, action) => {
  switch (action.type) {
    case ACTIVATE_LOCATION:
      return state.set('activeLocationId', action.id);
    default:
      return state;
  }
};

Solution 2 - Javascript

Immutable.js ought to be used in each reducer as needed, e.g.

import {
    ACTIVATE_LOCATION
} from './actions';

import Immutable from 'immutable';

export let ui = (state, action) => {
    switch (action.type) {
        case ACTIVATE_LOCATION:
            state = Immutable
                .fromJS(state)
                .set('activeLocationId', action.id)
                .toJS();
            break;
    }

    return state;
};

However, there is a lot of overhead in this example: every time reducer action is invoked, it has to convert JavaScript object to an an instance of Immutable, mutate the resulting object and convert it back to JavaScript object.

A better approach is to have the initial state an instance of Immutable:

import {
    ACTIVATE_LOCATION
} from './actions';

import Immutable from 'immutable';

let initialState = Immutable.Map([]);

export let ui = (state = initialState, action) => {
    switch (action.type) {
        case ACTIVATE_LOCATION:
            state = state.set('activeLocationId', action.id);
            break;
    }

    return state;
};

This way, you only need to convert the initial state to an instance of Immutable ounce. Then each reducer will treat it as an instance of Immutable and pass it down the line as an instance of Immutable. The catch is that now you need to cast the entire state to JavaScript before passing the values to the view context.

If you are performing multiple state mutations in a reducer, you might want to consider batching mutations using .withMutations.

To make things simpler, I have developed a redux-immutable library. It provides combineReducers function thats equivalent to the function of the same name in the redux package, except that it expect the initial state and all reducers to work with Immutable.js object.

Solution 3 - Javascript

If you're just looking for an easy way to make updates without mutation, I maintain a library: https://github.com/substantial/updeep which, in my opinion, is a good way to do that with redux.

updeep lets you work with regular (frozen) object hierarchies, so you can do destructuring, see objects in logs and the debugger, etc. It also has a powerful API that allows for batch updating. It's not going to be as efficient as Immutable.js for large data sets because it does clone objects if it needs to.

Here's an example (but check those in the README for more):

import {
    ACTIVATE_LOCATION
} from './actions';
import u from 'updeep';

export let ui = (state = [], action) => {
    switch (action.type) {
        case ACTIVATE_LOCATION:
            state = u({ activeLocation: action.id }, state);
            break;
    }

    return state;
};

Solution 4 - Javascript

The accepted answer should not be the accepted answer. You need to initialise state using immutable and then (as mentioned before) use redux-immutablejs

const initialState = Immutable.fromJS({}) // or Immutable.Map({})

const store = _createStore(reducers, initialState, compose(
    applyMiddleware(...middleware),
    window.devToolsExtension ? window.devToolsExtension() : f => f
));

Than use the combineReducers from redux-immutablejs

Extra tip: Using react-router-redux works pretty well so if you would like to add this then replace the reducer from react-router-redux with this:

import Immutable from 'immutable';
import {
    UPDATE_LOCATION
} from 'react-router-redux';

let initialState;

initialState = Immutable.fromJS({
    location: undefined
});

export default (state = initialState, action) => {
    if (action.type === UPDATE_LOCATION) {
        return state.merge({
            location: action.payload
        });
    }

    return state;
};

Just import this into your root reducer

This is also stated in the documentation of redux-immutablejs

Solution 5 - Javascript

Take a look at https://github.com/indexiatech/redux-immutablejs

It's pretty much a combineReducer and an optional createReducer that conforms with Redux standards.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionGajusView Question on Stackoverflow
Solution 1 - Javascriptuser5004821View Answer on Stackoverflow
Solution 2 - JavascriptGajusView Answer on Stackoverflow
Solution 3 - JavascriptAaron JensenView Answer on Stackoverflow
Solution 4 - JavascriptMichielView Answer on Stackoverflow
Solution 5 - Javascriptasaf000View Answer on Stackoverflow