question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

setState() mutations and redux mutations are not batched into a single re-render

See original GitHub issue

What is the current behavior?

See the codesandbox link below for a running example of the following description.

I have a redux store containing a single array of integers, elems, initialized to [0]. I have both an Outer component and an Inner component which are parameterized by elems via connect(). Inner stores an index into the array in this.state.index, initialized to 0. It also renders a button which, when clicked, will insert a new element into the redux and update this.state.index to be the index of the new elem.

When the button is pressed, the component is re-rendered twice. During the first re-render, this.state.index has changed, but not this.props.elems, so when I index the array, I receive an undefined result. The subsequent re-render works fine, however.

Note that if I remove the Outer component, and instead directly render Inner, there is no issue. Something about having both a parent and a child component subscribed to the same redux data is causing this issue.

If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem. Your bug will get fixed much faster if we can run your code and it doesn’t have dependencies other than React. Paste the link to a CodeSandbox (https://codesandbox.io/s/new) or RN Snack (https://snack.expo.io/) example below:

https://codesandbox.io/s/eager-kepler-coo8g

What is the expected behavior?

I would expect render() to be called only once, with both this.state and this.props updated to their most recent values.

Which versions of React, ReactDOM/React Native, Redux, and React Redux are you using? Which browser and OS are affected by this issue? Did this work in previous versions of React Redux?

See the codesandbox link above.

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:20 (9 by maintainers)

github_iconTop GitHub Comments

2reactions
afreixcommented, Aug 23, 2019

We are also experiencing the same issue where a set state call and redux dispatch are not batched into one re-render despite being called within an event handler

1reaction
ckedarcommented, Oct 18, 2019

I have spent some time digging into this issue and I will share my findings here:

The root cause of the issue is making the ConnectFunction a PureComponent by wrapping in React.memo().

Based on what I have read in blog, this step of making it pure was taken as part of performance optimization.

But this optimzation is too aggresive, rather improper because ConnectFunction consumes the state inside the store, which is ‘external’ to this component in the sense that it does not reflect in its props.

A workaround of using {pure:false} is available, but it is un-intuitive as your component that is being connected could be a genuine PureComponent but you will still encounter this bug.

Expectation when you are connecting your component is that your component will always be rendered with latest state from store, so this bug is breaking the basic promise of connect.

In v5.0.7, where Connect was a Class component, it was using shouldComponentUpdate to bail out of render when there is no change in component’s props. As Connect is implemented as Function component in v7, there is no mechanism available to bail out a waistful render. I guess making it pure was opted as workaround for this needed bailout.

But this workaround is over-agressive. In v5.0.7’s class component, componentWillReceiveProps, which is called in every render cycle, executes selector.run, which will re-evaluate mapStateToProps if either store state or wrapper props have changed. The bailout happens in shouldComponentUpdate only if merged props have not changed, so it is conditional as opposed to unconditional bailout in v7’s function component wrapped in React.memo.

However, wrapping the ConnectFunction in React.memo gives a huge performance boost. Removing the React.memo() results in drastic drop in performance, almost to the level of v6. I have verified this with react-redux-benchmarks. So it seems there is no clean solution to this problem.

The main motiviation behind move from v5 to v6 and onwards, I believe, was to move from Legacy Context API to New Context API. Usage of function component is strictly not necessary to achieve this. So we could go back to Class component for Connect while using New Context API and continue to use shouldComponentUpdate bailout.

Of course this will bring back StrictMode warnings, but then question is what is more important - StrictMode compliance or bug free behaviour while keeping performance at acceptable level?

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why can't I directly modify a component's state, really?
This answer is to provide enough information to not change/mutate the state directly in React. React follows Unidirectional Data Flow.
Read more >
React setState usage and gotchas - ITNEXT
setState() will always lead to a re-render unless shouldComponentUpdate() returns ... Till React 16, there is no batching by default outside of React...
Read more >
A (Mostly) Complete Guide to React Rendering Behavior
Render batching is when multiple calls to setState() result in a single render pass being queued and executed, usually on a slight delay....
Read more >
React state batch updating - reduce re-renders | The Startup
We change the state by calling setState or using useState . These changes cause parts of the component to re-render, and possibly to...
Read more >
Hooks API Reference - React
React may group several state updates into a single re-render to improve ... Mutations, subscriptions, timers, logging, and other side effects are not...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found