Search code examples
reactjsreduxreact-hooksreact-reduxrerender

Component rerendering when non-related parts of redux store update in react-redux


I notice that some of my components are rerendering when I wouldn't expect them to. Lets say I have a <Header /> component. <Header /> calls useSelector, and grabs a slice of state. As an example, my whole redux store may look like this:

{
  header: "header slice",
  body: "body slice"
}

<Header /> grabs what it needs with use selector:

const Header = () => {
  const headerSlice = useSelector(state => state.header);
  return <div>{headerSlice}</div>;
}

Elsewhere in the application, something causes the body slice to update. When this happen, I see that <Header /> rerenders. This is not what I would expect. Why might this be happening?

For more detail, I was able to track this down by running the devtools profiler while triggering the action that updates state.body. I see that <Header /> rerenders, and my profiler tells me its due to "Hooks 4, 9, 14, 19, and 24" changed.

enter image description here

When I look at that component's hooks in devtools, I see that all these hooks seem to be part of a Selector -> SyncExternalStoreWithSelector:

enter image description here

But there's not much information here to tell me which selector or was, what the previous value was, etc. When I remove these selectors, the unnecessary rerenders stop. But the values that this component consumes are not the values that are updating. I have tried wrapping the component in React.memo, and it makes no difference. I have not experienced this problem before with redux. Usually store updates only cause rerenders in those components that useSelect updated values.

I am using react 18.2.0, redux 4.2.0, react-redux 8.0.2, and webpack 5.


Solution

  • The first answer here is to use the Redux DevTools and look at what state is changing in the Redux store after each dispatch. Then, compare the state changes vs what values this component is selecting.

    The next step is to look at the actual selectors in this component file. Are any of them accidentally returning new references? A common example might be state => state.todos.map() or .filter() or similar, which return new arrays. If those aren't memoized, those will cause unnecessary re-renders even if the original data didn't change.