I'm trying to use ImmutableJS with my React / Flux application.
My stores are Immutable.Map
objects.
I'm wondering at which point should I use .toJS()
? Should it be when the store's .get(id)
returns ? or in the components with .get('member')
?
toJS() converts plain JS objects with a length property, in an Immutable collection, to an array (v4) #1438.
Immutable. js is a library that supports an immutable data structure. It means that once created data cannot be changed. It makes maintaining immutable data structures easier and more efficient.
An immutable variable can never be changed. To update its value, you create a new variable. The same applies to objects and arrays. Instead of changing an array, to add a new item you create a new array by concatenating the old array, plus the new item.
fromJS() Deeply converts plain JS objects and arrays to Immutable Maps and Lists.
Ideally, never!
If your Flux stores are using Immutable.js then try to maintain all the way through. Use React.addons.ReactComponentWithPureRenderMixin to achieve a memoization performance win (it adds a shouldComponentUpdate methods).
When rendering, you may need to call toJS()
as React v0.12.x only accepts Array
as children:
render: function () { return ( <div> {this.props.myImmutable.map(function (item) { <div>{item.title}</div> }).toJS()} </div> ); }
This have changed in React v0.13.x. Components accept any Iterable as children instead of only Array
. Since Immutable.js implements Iterable, you are able to omit the toJS()
:
render: function () { return ( <div> {this.props.myImmutable.map(function (item) { <div>{item.title}</div> })} </div> ); }
Kinda old question but lately I've been experimenting with this approach using reselect and lodash's memoize in the effort of returning comparable objects to React's Components.
Imagine you have a store like this:
import { List, Map } from 'immutable'; import { createSelector } from 'reselect'; import _ from 'lodash'; const store = { todos: List.of( Map({ id: 1, text: 'wake up', completed: false }), Map({ id: 2, text: 'breakfast', completed: false }) ) }; const todosSelector = state => state.todos; function normalizeTodo(todo) { // ... do someting with todo return todo.toJS(); } const memoizeTodo = _.memoize(normalizeTodo); export const getTodos = createSelector( todosSelector, todos => todos.map(memoizeTodo) );
Then I pass to a TodoList
component todos
as a prop, which will then be mapped into two TodoItem
Components:
class TodoList extends React.Component { shouldComponentUpdate(nextProps) { return this.props.todos !== nextProps.todos; } render() { return (<div> {this.props.todos.map(todo => <TodoItem key={todo.id} todo={todo} />)} </div>); } } class TodoItem extends React.Component { shouldComponentUpdate(nextProps) { return this.props.todo !== nextProps.todo; } // ... }
This way, if nothing changed in the todo's store, when I call getTodos()
reselect returns to me the same object, and so nothing gets re-rendered.
If, for example, todo with id 2
is marked as completed, it also changes in the store, and so a new object is returned by todosSelector
. Then the todos are mapped by memoizeTodo
function, which should return the same object if a todo isn't changed (since they are Immutable Maps). So when TodoList
receives the new props, it re-renders because todos
has changed, but only the second TodoItem
re-renders because the object representing the todo with id 1 didn't change.
This surely could lead to a performance loss, especially if our store contains a lot of items, but I didn't notice any problem in my medium-size app. The upside of this approach is that your Components receives plain javascript objects as props and could use them with something like PureRenderMixin
, so how objects are returned from the store is not business of Components anymore.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With