Redux: Slow Performance Observed When Updating State On Keyup

Created on 5 Sep 2015  路  18Comments  路  Source: reduxjs/redux

Hi all:

problem statement

On each keystroke trigger an action, reduce the state to compensate for the new character, but observe that re-rendering becomes laggy. You can watch the lag here.

background

  • I use react-router, and have a simple component structure (I'd snap a shot of the component DOM, but electron react-dev tools is currently out-of-operation). It's a few layers deep:
Provider >
App >
Dashboard  >
Home {connected, but passes no state/props through children. why? to dispatch an auth middleware event on load} >
ProjectDashboard >
ProjectController {connected, passes a single object of state, ~small} >
Project {dumb}

a third component is connected, but is not rendered. its a layout component for login, and I don't believe to be associated with the issue at hand.

I've profiled while punching keys into the field. It's the name field shown. It looks like a ton of time is being spent in validate and doing deepEquals in shouldComponentUpdate.
screen shot 2015-09-04 at 6 25 01 pm
screen shot 2015-09-04 at 8 34 33 pm

discussion

  • it's been noted to move components down lower in the structure if perf is an issue, so I've done that. i only have a few components, and they pass the minimal amount of state via connect
  • my reducers and state updating is waaay down the list of processing % time. it seems to correspond to actual react re-rendering time in some components, perhaps performing frivolous checks?

Would love some tips! Thanks!

question

Most helpful comment

90% of folks that experience this issue aren't building their react bundle/compile with the production flag on. And another interesting fact of the day, 99% of statistics like my statement are made up! Haha

Best resolution if your building your React app code with grunt or something:

export NODE_ENV=production && grunt

That usually does the trick, IME

All 18 comments

What is contained in props on your Project component? It looks like you've got something big in your props that's being validated.

I'd recommend grabbing the react devtools, as it makes viewing this stuff much easier.

Could you post your Project component's code?

I get similar performance issues with forms when I'm using redux-devtools.

Have you tried out reselect? It'll memoize selectors, which should help you avoid re-calculating stuff when data changes.

Aside from that, pulling in react-pure-render might help as well.

hi @timdorr @cesarandreu. thanks for your willingness to add ideas!

  1. my selected-mapped state isn't tiny, but it's not huge either. see here. I could perhaps reduce some of that data further. do you know much about the what and why of the validation step?
  2. i have tried to get react devtools up, but it's currently out-of-order in electron, which is my target client.
  3. in regards to reselect, i'm not modifying as I select my state-bits into the component's stateToProps map. but perhaps therein is part of the issue? i'm still reasoning over why you'd cache in this part of the processes vs. before/conditionally dispatching an action.
  4. i will be trying pure render for sure
    thanks guys! would definitely care for some more tips if you got them!

It's a known issue with Redux DevTools and if you'd like to see it fixed, a good first step is to contribute tests. You won't have this issue in production.

There's a PR attempted to fix it: https://github.com/gaearon/redux-devtools/pull/133
Can you please test whether it improves the situation?

This is fixed in [email protected].
Performing an action with DevTools open was O(n), now it's O(1).

thanks @gaearon! will try at my next opportunity

I'm still seeing a bit of a lag when carrying out rapid state changes on the latest version (in this scenario it's a keydown event for an autocomplete). My suspicion is that it may be due to the number of LogMonitor nodes it has to update in quick succession. I created a very simple custom monitor which just logged the action and the problem seemed to go away. If anyone else is experiencing this I think the resolution may be one of the following..

  • Create a simple custom monitor with an emphasis on speed
  • In the default LogMonitor raise a PR to only retrieve action details on click

(I can hopefully help out with a resolution, I wonder if this issue could be transferred to redux-devtools)

Oh, I see now. There hasn't been much effort to optimize LogMonitor so I wouldn't be surprised if there are many low hanging fruit there.

Adding a shouldComponentUpdate that does a shallowEqual for LogMonitorEntry seems to greatly improve responsiveness for me when using redux-forms (goes from unusable to usable). I'm not entirely sure if it's safe to just do a shallowEqual though... so far seems ok.

@davecoates Then please file an issue (or, better, a PR) to add it!

@davecoates The issue I found with redux-form is that onChange', 'onBlur', 'onFocus', 'onUpdate',
'handleChange', 'handleBlur', 'handleFocus' on field prop change on every render. I put a shouldComponentUpdate on my Widget component (that render a input, select or whatever) to ignore changes of those functions. This is what I use:

const IGNORE_FUNCTIONS = [
  'onChange', 'onBlur', 'onFocus', 'onUpdate',
  'handleChange', 'handleBlur', 'handleFocus']


export function shallowEqual(objA, objB) {
  if (objA === objB) {
    return true
  }

  if (typeof objA !== 'object' || objA === null ||
      typeof objB !== 'object' || objB === null) {
    return false
  }

  const keysA = Object.keys(objA)
  const keysB = Object.keys(objB)

  if (keysA.length !== keysB.length) {
    return false
  }

  // Test for A's keys different from B.
  const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB)
  for (let i = 0; i < keysA.length; i++) {
    if (!bHasOwnProperty(keysA[i])) {
      return false
    }
    if (keysA[i] === 'field') {
      if (!shallowEqual(objA[keysA[i]], objB[keysA[i]])) {
        return false
      }
    } else if (typeof objA[keysA[i]] === 'function' &&
      IGNORE_FUNCTIONS.indexOf(keysA[i]) > -1 ) {
      // don't compare function is the IGNORE_FUNCTIONS array
      continue
    } else if (objA[keysA[i]] !== objB[keysA[i]]) {
      // console.log('rerender because of prop named:')
      // console.log(keysA[i])
      // console.log('before:')
      // console.log(objA[keysA[i]])
      // console.log('after:')
      // console.log(objB[keysA[i]])
      return false
    }
  }

  return true
}


export default function shouldPureComponentUpdate(nextProps, nextState) {
  return !shallowEqual(this.props, nextProps) ||
         !shallowEqual(this.state, nextState)
}

class Widget extends Component {
  static propTypes = {
    field: PropTypes.object.isRequired,
  }
  shouldComponentUpdate = shouldPureComponentUpdate
}


@vincentfretin Please file an issue in Redux Form. You shouldn't have to do these optimizations by hand.

Redux DevTools 3.0 Beta 2 should vastly improve performance.
Please help test it: https://github.com/gaearon/redux-devtools/releases/tag/v3.0.0-beta-2

I'm still seeing the same issue in 3.0.5 for onChange.

@stanleycyang Please share a project reproducing the problem.

Edit: this is for DevTools. Just noticed this is in the redux ticket, as it was the first thing that popped up when I googled the two terms. I can move it if you'd prefer.

git clone https://github.com/erikras/redux-form
git checkout gh-pages
npm install
npm run dev

Navigate to http://localhost:3030/#/examples/synchronous-validation

Hold down the spacebar in the username field

Remove the DevTools from the project: src/index.js by deleting the following line

{devToolsEnabled && !window.devToolsExtension && <DevTools/>}

Refresh

Notice speedup.

You can also get a preview here: http://erikras.github.io/redux-form/#/examples/synchronous-validation?_k=hl6zzk

This is not just keyup for me. DevTools is slow for everything.

90% of folks that experience this issue aren't building their react bundle/compile with the production flag on. And another interesting fact of the day, 99% of statistics like my statement are made up! Haha

Best resolution if your building your React app code with grunt or something:

export NODE_ENV=production && grunt

That usually does the trick, IME

Was this page helpful?
0 / 5 - 0 ratings