Skip to content

Immutable as React state

Doug Hoyte edited this page Sep 29, 2017 · 13 revisions

This is a contrived example of using Immutable structures as React state.

This example assumes React v0.13 or higher. v0.13 provides the functional setState API and allows for Iterables to be used as React children. This example also uses some ES6 features (destructuring, and arrow functions).

Note that state must be a plain JS object, and not an Immutable collection, because React's setState API expects an object literal and will merge it (Object.assign) with the previous state.

Try this out in this jsbin. Note, this is using JavaScript ES6 syntax (compiled by Babel).

var React = require('react');
var { Map, List } = require('immutable');

var Component = React.createClass({

  getInitialState() {
    return {
      data: Map({ count: 0, items: List() })
    }
  },

  handleCountClick() {
    this.setState(({data}) => ({
      data: data.update('count', v => v + 1)
    }));
  },

  handleAddItemClick() {
    this.setState(({data}) => ({
      data: data.update('items', list => list.push(data.get('count')))
    }));
  },

  render() {
    var data = this.state.data;
    return (
      <div>
        <button onClick={this.handleCountClick}>Add to count</button>
        <button onClick={this.handleAddItemClick}>Save count</button>
        <div>
          Count: {data.get('count')}
        </div>
        Saved counts:
        <ul>
          {data.get('items').map(item => 
            <li>Saved: {item}</li>
          )}
        </ul>
      </div>
    );
  }

});

React.render(<Component />, document.body);

Using individual values from Immutable state.

There is nothing React specific about using Immutable data in React. Just use the Immutable.js read API, most often get() and getIn().

  render() {
    var data = this.state.data;
    return (
      <div>
        <div onClick={this.handleCountClick}>
          Count: {data.get('count')}
        </div>

Using collections of values from Immutable.js

Whether they come from state or props, Immutable collections are often used to produce a React element's children elements. The best way to do this is by taking advantage of the higher-order functions in Immutable.js such as filter(), reduce(), and map(), amongst others.

<ul>
  {data.get('items').map(item => 
    <li>Saved: {item}</li>
  )}
  <li onClick={this.handleAddItemClick}>Save count</li>
</ul>

Here we get the 'items' key from our Immutable data which will be an Immutable List, and then use map() to build a new List of React elements, one for each item in the original list.

Note: If you're using React v0.12, you must add .toArray() after the call to .map() to convert the Immutable collection to a JavaScript Array. The ability to use any Iterable as React children was added in React v0.13.

Updating individual values in Immutable state.

Use React's setState API to update state, but provide a function which returns a state update object. For illustrative purposes, let's unpack a piece of this code that's both important to the example and takes advantage of ES6 syntax:

handleCountClick() {
  this.setState(({data}) => ({
    data: data.update('count', v => v + 1)
  }));
}

This will result in incrementing the count key of our immutable data, and then updating React's state with our new immutable data.

React's setState accepts a function, a new feature added in React v0.13 which takes the previous state as an argument and returns an object of state keys to update. Immutable.js's update function takes a key ('count' in this case), and an updating function which takes the previous value at that key and returns its new value. The update method is a nice shortcut for using get and set together. Here's that same example using get and set:

handleCountClick() {
  this.setState(({data}) => ({
    data: data.set('count', data.get('count') + 1)
  }));
}

Note that Immutable.js has a rich API and update and set are a couple of many methods you may use regularly.

This is using ES6 arrow functions, and destructuring assignment, it's equivalent to the ES5 code:

handleCountClick() {
  this.setState(function (prevState) {
    var data = prevState.data;
	return {
      data: data.update('count', function (v) { 
        return v + 1; 
      })
    };
  });
}

Updating multiple values in Immutable state.

Need to update multiple values? Since the .update() and .set() methods return new values, they're naturally chainable:

handleCountClick() {
  this.setState(({data}) => ({
    data: data
      .update('count', v => v + 1)
      .set('otherProp', 'newValue')
  }));
}

Updating collections of values in Immutable state.

Similar to updating individual values, however we use the Immutable.js List API methods, like push() or unshift().

handleAddItemClick() {
  this.setState(({data}) => ({
    data: data.update('items', list => list.push(data.get('count')))
  }));
},

Again, here's what that setState call looks like using function expressions.

this.setState(function (prev) {
  var data = prev.data;
  return {
    data: data.update('items', function (list) {
      return list.push(data.get('count'));
    })
  };
});

Helper function for setState pattern

If this pattern is prevalent in your code, it may be helpful to define a helper function:

setImmState(fn) {
  return this.setState(({data}) => ({
    data: fn(data)
  }));
}

Now you can write this same example as:

  handleCountClick() {
    this.setImmState(d => d.update('count', v => v + 1));
  },

  handleAddItemClick() {
    this.setImmState(d => 
      d.update('items', list => list.push(d.get('count')))
    );
  }