React-redux-form: Performance issue: [Violation] 'focus' handler took 603ms

Created on 30 May 2017  路  8Comments  路  Source: davidkpiano/react-redux-form

The Problem

In a table with 20 lines of 15 inputs (300 inputs), it takes half a second to just pass from one text input focused to another, and up to 3 to open a dropdown. Chrome devtools warn about 'focus' and 'blur' events taking half a second, although I am ignoring them using the ignore prop of Control components, so it is unexpected.

Edit: The problem simplifies itself to: "Why is there a "rrf/batch" (blur + change) action sent when only focusing an input, even when the value does not change, and how can I disable this behavior"?

2017-05-30_15-13-37

2017-05-30_15-12-59

This is what React is doing during one of these "events":

2017-05-30_15-31-21

From this I am not able to see if the performance impact is due a bug with the event handlers or a React tree reconciliation problem. Since I never change the input values, I don't understand why there should be rendering anyway.

Is it known/expected/a misuse of react-redux-form?

N.B. Performance with a big number of inputs is a problem that has apparently been studied and solved in redux-form, see https://github.com/erikras/redux-form/issues/529. As far as I know the idea is to decouple the form model from the form values.

Steps to Reproduce

My inputs are all of one of these forms:

<Control.text model={modelName} updateOn="blur" ignore={['focus']} />
<Control.input model={modelName} type="number" updateOn="blur" ignore={['focus']} />
<Control.input model={modelName} type="date" updateOn="change" ignore={['focus', 'blur']} />
<Control.checkbox model={modelName} updateOn="change" ignore={['focus', 'blur']} />
<Control.select model={modelName} updateOn="change" ignore={['focus', 'blur']} >
    {options}
 </Control.select>

put in a standard HTML table, itself in a

connected to Redux.

Note that I make text fields update only onBlur, and try to ignore focus and blur events for the others. I have only PureComponents, disabled react-logger, and followed all performance advice I could find. Yet it remains extremely slow.

Reproducible Code Example

(I will spend a couple hours writing and example only if the description above suggests a bug and not a misuse.)

Most helpful comment

@jdelafon It's because of the way React works - since it's a tree model, any update in state will cause _all_ of the child components to ask whether or not they should update.

What React is smart at is knowing when not to update (and this is tweakable in shouldComponentUpdate).

What React is _not_ smart at is knowing when not to ask when to update. Calling sCU for every single child element, even if it returns false for 99% of them, can be very slow. You're still calling a function 600+ times (in this case).

With the way React works, it's definitely not easy to say "I'm only going to update this one component - all you other components, don't even bother asking if you should update."

A functional/reactive paradigm is what spreadsheets, like Excel, would use. Every cell explicitly knows what other cell/entity to _subscribe_ to, and knows that whenever that thing that it's subscribed to emits an event, it will update. No other cell in the spreadsheet will even blink an eye. It's push-based, not pull-based.

All 8 comments

Is this still an issue? You closed the ticket

I wanted to open a more precise one when I really figure it out. I think this is the problem:

  • I want to re-render the whole form only if a value changes;
  • Inputs update the store on blur (updateOn="blur"), otherwise the performance is awful;
  • Even if you don't change the value, the onBlur event still triggers (clicking inside one text field, then inside another);
  • When the onBlur triggers, even if the value did not change (still ""), it re-renders the form. React-logger says there is "-- no diff --" when the "rrf/batch" (blur + change) event is fired, yet the whole thing re-renders. I don't understand this.

Even if you don't change the value, the onBlur event still triggers (clicking inside one text field, then inside another);

This is expected behavior - we can't make a restrictive assumption on this.

When the onBlur triggers, even if the value did not change (still ""), it re-renders the form.

This could be because the field state is changing touched: false to touched: true, but I'll investigate why it rerenders if you can provide me a code example.

I wrote a minimal example and the form does not re-render. Performance is great even with 600 inputs. I could find my mistake that caused the re-render. My bad. Good job with this library.

@jdelafon That's awesome to hear! Maybe you were on an older version? I know that we've made a _ton_ of performance improvements in the last few minor versions.

Latest version. My component is still very slow but I figured out that it was because of the dropdown inputs. If I replace them by text inputs it becomes much faster, and apparently most of the time is spent in DOM reconciliation (if it makes any sense), so nothing I can really do about it.

What bothers me in this is that something like Excel is able to deal with way many more inputs without any lag, so why can't we do something similar with React? It is not even close. Or maybe I just suck.

@jdelafon It's because of the way React works - since it's a tree model, any update in state will cause _all_ of the child components to ask whether or not they should update.

What React is smart at is knowing when not to update (and this is tweakable in shouldComponentUpdate).

What React is _not_ smart at is knowing when not to ask when to update. Calling sCU for every single child element, even if it returns false for 99% of them, can be very slow. You're still calling a function 600+ times (in this case).

With the way React works, it's definitely not easy to say "I'm only going to update this one component - all you other components, don't even bother asking if you should update."

A functional/reactive paradigm is what spreadsheets, like Excel, would use. Every cell explicitly knows what other cell/entity to _subscribe_ to, and knows that whenever that thing that it's subscribed to emits an event, it will update. No other cell in the spreadsheet will even blink an eye. It's push-based, not pull-based.

Very interesting, thanks! I am reading about RxJS now, it may be the answer.

Was this page helpful?
0 / 5 - 0 ratings