-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Immutable as React state
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);
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>
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.
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;
})
};
});
}
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')
}));
}
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'));
})
};
});
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')))
);
}