Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React slow with lots of textareas (but not text input fields)

Tags:

reactjs

With lots of textareas, my (admittedly large) form becomes sluggish.

With text input fields instead, however, everything is snappy.

I've created two minimized testcases, one with textareas and one with input fields. Click the toggle button to show/hide a group to see the issue. With Chrome, at least, the group is really slow to toggle in the textarea version.

2500 textareas: http://jsbin.com/wudujusoji/edit?html,js,output

2500 text input fields: http://jsbin.com/rezudotojo/edit?html,js,output

What am I doing wrong here? Shouldn't the textarea and text input field versions be roughly the same performance?

like image 882
Esseb Avatar asked Jun 27 '15 14:06

Esseb


1 Answers

Let's make the test reasonable and add a shouldComponentUpdate to the Input component in each.

var Input = React.createClass({
  shouldComponentUpdate: function(nextProps){
    return nextProps.value !== this.props.value;
  },

console.time() is good if you're trying to measure some math or similar, but to really see what's happening here we need to use the profiler.

We'll start with the <input>s. I did this a few times, and it's always about 14ms for me.

The gray bar to the right labeled '(program)' is the browser doing its own work after the JS has returned. Here it's taking about 17ms to prepare for rendering. After that is idle where it's doing pretty much nothing except painting. Cool, this isn't bad considering the number of elements.

profiling the input example in chrome dev tools

The textarea is much more interesting. React averages around 30ms in these, which is fine considering it's the development build. I have no idea why chrome is taking 500ms to process these changes.

profiling the textarea example in chrome dev toolsanother profile of textareayet another textarea profile

I thought React must be doing something to cause this, so I (hackily) added a MutationObserver.

var mObserver = new MutationObserver(function(mutations) {
  mutations.forEach(function(mutation, i) {
    console.log('mutation ' + i + ': ' 
                + mutation.type 
                + ', at ' 
                + mutation.target.parentNode.tagName 
                + ' > ' 
                + mutation.target.tagName 
                + '[' 
                + [].indexOf.call(mutation.target.parentNode.children, mutation.target) 
                + ']'
                + ', on attribute ' + mutation.attributeName
                + ', from ' + mutation.oldValue 
                + ' to ' + mutation.target.getAttribute(mutation.attributeName)
               );

  });    
});
var moConfig = { attributes: true, childList: true, characterData: true, subtree: true };

React.render(React.createElement(Editor, null),
    document.querySelector('.editor'), 
    function(){
        var root = document.getElementById('root');
        if (!root) throw new Error('no root');
        mObserver.observe(root, moConfig);
    }
);

And when clicking the button it shows this:

mutation 0: attributes, at DIV > DIV[0], on attribute class, from null to category
mutation 1: childList, at DIV > BUTTON[0], on attribute null, from null to null

So yeah... I'm pretty sure changing the class name of a div and the text content of the button causes it to spend 500ms updating. My guess is it's all spent trying to layout the textareas.


As always, the answer to "Rendering 2,000 nodes when I do X" is to not render 2,000 nodes; X (e.g. React) doesn't matter.

Actually, slightly over 5,000 here, but who's counting?

like image 147
Brigand Avatar answered Oct 02 '22 11:10

Brigand