51

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.

Dan Abramov
  • 264,556
  • 84
  • 409
  • 511
Gajus
  • 69,002
  • 70
  • 275
  • 438
  • 1
    You can see how it's done here https://github.com/este/este – Restuta Sep 26 '15 at 17:58
  • 3
    @Restuta Stack Overflow rules require that you disclose your relation with a third-party product that you recommend/refer to/advertise, http://meta.stackexchange.com/questions/94022/how-can-i-link-to-an-external-resource-in-a-community-friendly-way In this case, you are one of the contributors. – Gajus Nov 19 '15 at 16:11
  • 35
    I don't think OSS boilerplate project qualifies for a "3rd party product" and my comment qualifies to be "an answer". It's just a comment, if you look closely my contribution to that project was one line that was later reverted. I am sorry, but I can't keep such a minor things in mind all the time I refer to something (since o contribute a lot). I admit I am a contributor, though. Not sure what was the intent of your comment, do you feel like I am advertising it? Take my word – I am not, I just saw implementation there that is working and idiomatic. – Restuta Nov 20 '15 at 19:04
  • You may also check out https://github.com/engineforce/ImmutableAssign, which is a lightweight immutable helper that allows you to continue working with POJO (Plain Old JavaScript Object). – engineforce Jun 22 '16 at 13:38

5 Answers5

48

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;
  }
};
  • 3
    Can you elaborate on why you believe this approach wont work with combineReducers? I've tested this, and as far as I can tell (also through reading the code for combineReducers you linked) - there shouldn't be anything that prevents this from working. –  Nov 16 '15 at 16:44
  • It does seem to work for me with `combineReducers` as well. @GajusKuizinas can you please elaborate why it won't work? – Junaid Nov 17 '15 at 10:43
  • 3
    I can confirm. It doesn't work, even with Redux above 3.x when whole state is actually Immutable.Map. One if it's keys can be Immutable.Map, but not the state itself. – dakt Jan 22 '16 at 21:04
  • Are you talking about the top level state tree? as in the final output from create store after combining reducers? Because you can definitely feed reducers that return immutable.js data structures into combine reducers. –  Jan 22 '16 at 22:06
  • I'm talking about this: combineReducers(Immutable.Map({a: redcr1, b: redcr2}); – dakt Jan 23 '16 at 11:32
  • Yeah you would never do that as there's no need to - the answer I gave does work with combineReducers because when used it's combineReducer({ fn1: fn1 (which when called will return an immutable.js state object}); –  Jan 23 '16 at 17:56
  • 2
    @dakt I have updated `redux-immutable` implementation to address this specific issue without other side effects, https://github.com/gajus/redux-immutable#problem. – Gajus Jan 27 '16 at 18:51
25

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.

Gajus
  • 69,002
  • 70
  • 275
  • 438
  • 3
    Shouldn't the state contain a reference to the immutable object? Using Immutable to convert an object to an Immuttable and then back to an object seems to lose some of the benefits of Immutable. For example, the ability to make future changes to the Immutable object, in which the Immutable.js library will efficiently make those changes for you. Thoughts? – richardaday Aug 08 '15 at 18:27
  • @richardaday I am assuming that you are referring to `redux-immutable` implementation. If yes, its best to open an issue under the repo. – Gajus Aug 08 '15 at 18:59
  • Shouldn't it be new_state = state.set('activeLocationId', action.id) and then return new_state ? – Dan Mazzini Aug 11 '15 at 18:38
  • @danmaz74 I missed the assignment. `state = state.set('activeLocationId', action.id);` is sufficient. No need for new variable name. – Gajus Aug 11 '15 at 19:00
  • just to let you guys know, if you're using typescript, fromJS...toJS... will strip all the type information, meaning you can then break the interfaces you applied, so it's not typesafe – Christopher Thomas Feb 16 '16 at 20:22
  • Gajus --- can you show an example where initialState is NOT an empty object. Being that it is an initialState - I usually like to have defaults in there. Can you show an example of this implementation? – james emanon Jul 30 '16 at 20:49
8

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;
};
Aaron Jensen
  • 6,030
  • 1
  • 30
  • 40
  • What advantage does this have over using Immutable.js? – Gajus Aug 08 '15 at 17:30
  • tl;dr: pro: debugging, destructuring, better api (imo) See the motivation section: https://github.com/substantial/updeep#motivation – Aaron Jensen Aug 08 '15 at 18:08
  • [It appears](https://github.com/substantial/updeep/graphs/contributors) you are affiliated with updeep. Please note that our [self-promotion policy](http://meta.stackexchange.com/questions/57497) requires you to disclose this information in answers like this one. – josliber Dec 23 '15 at 06:49
  • Thanks @josilber, affiliation added. – Aaron Jensen Dec 24 '15 at 07:23
5

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

Jason Moore
  • 7,169
  • 1
  • 44
  • 45
Michiel
  • 1,061
  • 10
  • 17
  • 1
    Note that `redux-immutable` is now officially preferred to `redux-immutablejs`. See https://github.com/gajus/redux-immutable/issues/12#issuecomment-176189454 and http://redux.js.org/docs/introduction/Ecosystem.html#utilities – Gajus Feb 11 '16 at 08:07
  • 1
    Why do you have to use redux-immutable? – dardub Mar 07 '16 at 22:03
  • Redux already support Immutable, it's not necessary to use redux-immutable – gusgard Jan 20 '17 at 19:31
2

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.

asaf000
  • 606
  • 6
  • 13