Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

ReactJS. Quite slow when rendering and updating a simple list of 1500 <li> elements. I thought VirtualDOM was fast

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/

like image 406
skay- Avatar asked Jan 14 '15 21:01

skay-


People also ask

Is React fast or slow?

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.

Why is my React so slow?

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.

What makes ReactJS performance faster?

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.


2 Answers

If you do the following, you can cut the time down by a lot. It spends 25ms to 45ms to update for me.

  • use the production build
  • implement shouldComponentUpdate
  • update the state immutably
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)

like image 121
Brigand Avatar answered Oct 31 '22 19:10

Brigand


  1. 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.

  2. 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' : ''} />
    
  3. 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;
    }
    
like image 33
daniula Avatar answered Oct 31 '22 17:10

daniula