18

I am using react with redux and redux thunk. I have an action where I am making a network request. With the useSelector I am getting the data from redux store. I have a problem that the component rerenders every time when I dispatch the action. I want that the component only rerenders when data changes. I tried already use shallowEqual as the second argument for useSelector. But it does not work. I have compressed it to minimal example in this sandbox. In the console you can see that the component rerenders with every network request. Here is the codesandbox: https://codesandbox.io/s/useselector-js6j0?file=/src/App.js:885-1294

here is the code:

function LevelThree() {
  console.log(`Level three calls: ${++levelThreeCalls}`);
  const contextData = useSelector(state => state.data);
  console.log(contextData);
  const dispatch = useDispatch();
  useInterval(() => {
    dispatch(updateData());
  }, 1000);

  return (
    <div>
      <button onClick={() => dispatch(updateData())}>Change context</button>
      {contextData[0].userId}
    </div>
  );

}

Yilmaz
  • 35,338
  • 10
  • 157
  • 202
otto
  • 1,815
  • 7
  • 37
  • 63

2 Answers2

23

The reason that your component is re-rendering on every update is because you are fetching the data again and updating it in redux store,

Since the reference of data changes, the useSelector function returns you a new value and re-renders the component.

You can pass a deepEqual comparison function as a second argument to useSelector to avoid re-rendering it data hasn't changed

import _ from 'underscore';
...
const contextData = useSelector(state => state.data, _.isEqual);

However, you must carefully use this and measure how frequently your data will be updated otherwise you will add an added calculation in your code which won't even prevent a re-render many times

Working demo

SuccessFollower
  • 210
  • 4
  • 14
Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
5

When an action is dispatched, useSelector() will do a reference comparison of the previous selector result value and the current result value. If they are different, the component will be forced to re-render. If they are the same, the component will not re-render

Kaido Hussar
  • 51
  • 2
  • 2
  • Thank you! This comment helped me solve a totally different issue where it was taking 2 seconds for my clicks to register on the page. It turns out, I was dispatching an event every time the user clicks, so I can reset the auto-logout timer, but a few of my components have VERY expensive useSelectors, and they were all running immediately after dispatching the auto-logout timer reset event. – Ethan Dowler Sep 20 '22 at 13:11