I was really disappointed by the performance I got on the following simple ReactJS example. When clicking on an item, the label (count) gets updated accordingly. Unfortunately, this takes roughly ~0.5-1 second to get updated. That's mainly due to "re-rendering" the entire todo list.
My understanding is that React's key design decision is to make the API seem like it re-renders the whole app on every update. It is supposed take the current state of the DOM and compare it with the target DOM representation, do a diff and update only the things that need to get updated.
Am I doing something which is not optimal? I could always update the count label manually (and the state silently) and that will be an almost instant operation but that takes away the point of using ReactJS.
/** @jsx React.DOM */
TodoItem = React.createClass({
getDefaultProps: function () {
return {
completedCallback: function () {
console.log('not callback provided');
}
};
},
getInitialState: function () {
return this.props;
},
updateCompletedState: function () {
var isCompleted = !this.state.data.completed;
this.setState(_.extend(this.state.data, {
completed: isCompleted
}));
this.props.completedCallback(isCompleted);
},
render: function () {
var renderContext = this.state.data ?
(<li className={'todo-item' + (this.state.data.completed ? ' ' + 'strike-through' : '')}>
<input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
<span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
</li>) : null;
return renderContext;
}
});
var TodoList = React.createClass({
getInitialState: function () {
return {
todoItems: this.props.data.todoItems,
completedTodoItemsCount: 0
};
},
updateCount: function (isCompleted) {
this.setState(_.extend(this.state, {
completedTodoItemsCount: isCompleted ? this.state.completedTodoItemsCount + 1 : this.state.completedTodoItemsCount - 1
}));
},
render: function () {
var updateCount = this.updateCount;
return (
<div>
<div>count: {this.state.completedTodoItemsCount}</div>
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem) {
return <TodoItem data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
</div>
);
}
});
var data = {todoItems: []}, i = 0;
while(i++ < 1000) {
data.todoItems.push({description: 'Comment ' + i, completed: false});
}
React.renderComponent(<TodoList data={ data } />, document.body);
<script src="http://fb.me/react-js-fiddle-integration.js"></script>
jsFiddle link, just in case: http://jsfiddle.net/9nrnz1qm/3/
React is slow. I mean, any medium-size React application is slow. But before you start looking for alternatives, you should know that any medium-size Angular of Ember application is slow, too. And the good news is: If you care about performance, it's fairly easy to make any React application super fast.
Basically always when your React application is slow it's due to too many components re-rendering or too often. React have very useful Developer Tools, where you can profile your App and then find out which components are taking most of the time to render.
To optimize React rendering, you need to make sure that components receive only necessary props. It will let you control the CPU consumption and avoid over-rendering unnecessary features. The solution is to create a functional component that will collect all props and redistribute them to other components.
If you do the following, you can cut the time down by a lot. It spends 25ms to 45ms to update for me.
updateCompletedState: function (event) {
var isCompleted = event.target.checked;
this.setState({data:
_.extend({}, this.state.data, {
completed: isCompleted
})
});
this.props.completedCallback(isCompleted);
},
shouldComponentUpdate: function(nextProps, nextState){
return nextState.data.completed !== this.state.data.completed;
},
Updated fiddle
(there are a lot of questionable things about this code, daniula points out some of them)
When you are generating list of elements you should provide unique key prop for everyone. In your case:
<ul className="todo-list">
{ this.state.todoItems.map(function (todoItem, i) {
return <TodoItem key={i} data={ todoItem } completedCallback={ updateCount } />
}) }
</ul>
You can find out about this mistake by warning message in browser console:
Each child in an array should have a unique "key" prop. Check the render method of TodoList. See fb.me/react-warning-keys for more information.
There is another warning which you can easily fix by changing event handler on <input type="checkbox" />
inside <TodoItem />
from onClick
to onChange
:
<input onClick={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
You are doing some string concatenation to set proper className
. For more readable code try using nice and simple React.addons.classSet:
render: function () {
var renderContext = this.state.data ?
var cx = React.addons.classSet({
'todo-item': true,
'strike-through': this.state.data.completed
});
(<li className={ cx }>
<input onChange={this.updateCompletedState} type="checkbox" checked={this.state.data.completed ? 'checked' : ''} />
<span onClick={this.updateCompletedState} className="description">{this.state.data.description}</span>
</li>) : null;
return renderContext;
}
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