Are you using useSelector correctly in Redux?

Hello everyone, this article aims to add some understanding when using the useSelector hook in development. I hope I managed to reveal some part of the information below that will help developers who did not know this information before.

Everyone who uses Redux in React knows one way or another that useSelector is one of the main hooks for getting data from a Redux store in React components and custom hooks. At the same time, some developers are not even aware of the possible problems that may arise in the work if they do not think about some development points using this hook.

We will talk about problems that may arise when destructuring data from a store using useSelector.

Impact of destructuring

As we know, when using useSelector , we pass a selector function to it, with the help of which we select the necessary data from the Redux store. When using destructuring, we extract specific properties from the Redux state object. But we should not forget about the main problem of this approach – if any property of the object that we are destructuring changes (even if this property was not assigned to a variable during destructuring), additional component redraws occur, even if the property changed in a completely different component.

Let’s try to briefly explain why this happens. When we extract a part of an object from a store using destructuring, we create a new object that has a different reference than the object being destructured. At the same time, when the properties of an object in the store change, a new object (new link) is created in the destructuring, which leads to an additional re-rendering of the component.

How to avoid unnecessary renders?

One of the main ways to avoid this problem is to directly assign properties from the store to variables instead of using destructuring.

Kind people on the internet have written a little code that demonstrates the behavior of both cases. I am attaching a sandbox so that everyone can see and play with it.

Zhmak

Let’s see what happens in this code.

Initially, we have three components that are rendered when the page loads. In each of these components, there is a receipt of data from the store in a different way. In this case, the store contains two values: firstCounter and secondCounter.

Components
const { firstCounter } = useSelector((store) => store.counters);
console.log("render MethodOne");
const firstCounter = useSelector((store) => store.counters.firstCounter);
console.log("render MethodTwo");
const { firstCounter } = useSelector((store) => ({
    firstCounter: store.counters.firstCounter
  }));
console.log("render MethodThree");

We see that the first and third components use destructuring in two ways, while the second component uses direct assignment.

When you increase firstCounter, you can see that everything works as we need – the value increases by 1 and at the same time, 1 render occurs in each component.

Increasing firstCounter by 1

Now let’s try to increase secondCounter. And what do we see? Increasing secondCounter resulted in additional rendering of the first and third components, although it is not even used there. In the second component, the number of renders remained unchanged, from which we can conclude that direct assignment eliminates this problem.

Increasing secondCounter by 1

Maybe there are other ways?

Indeed, there are several other ways that have a place to be.

1) You can use the shallowEqual function from the react-redux package to compare objects from the store. It is passed as the second argument to useSelector. Let’s try to add this function to the above code.

const { firstCounter } = useSelector(
    (store) => ({
      firstCounter: store.counters.firstCounter
    }),
    shallowEqual
  );
console.log("render MethodThree");

In this case, indeed extra renders have been eliminated.

const { firstCounter } = useSelector((store) => store.counters, shallowEqual);
console.log("render MethodOne");

But in this case, additional renders were not eliminated. This is due to the fact that in the case of the third component, we directly pull out the firstCounter value from the store and put it in the object under the firstCounter key, after which the destructuring occurs. And, if firstCounter does not change in the store, then the link to it does not change either. In the second case, shallowEqual compares the references of the counters object, and when secondCounter changes, the reference still changes and an additional render occurs.

PS correct me if this is wrong.

2) Another way is to use the reselect library. In this case, the value will only change if it has been changed in the store. I will give a clumsy working example, perhaps there is a more convenient and rational way of using it, which I would also be glad to see.

const getFirstCounter = createSelector(
  (store) => store.counters.firstCounter,
  (firstCounter) => ({
    firstCounter
  })
);

const { firstCounter } = useSelector(getFirstCounter);

Total

From the above, it is clear that using destructuring is not the best way to get data from the store using useSelector, as this leads to unnecessary and hardly necessary renders.

As for me, the most convenient way is to simply assign directly. In all the examples given, it is clear that there is an addition of extra code that cannot be avoided, but the first method looks more readable and without additional troubles. But, as they say, the taste and color …

Additional Thoughts

Departing from the above, we can add that when using direct assignment, it is best to move all the functions passed to useSelector into a separate file and then use them. This can increase readability, reduce constant code duplication, and, in the end, if the name of a variable in the store changes, it will only need to be changed in one place, and not run through all the files looking for useSelector to change the name.

const getFirstCounter = (state) => state.counters.firstCounter;
const getSecondCounter = (state) => state.counters.secondCounter;

...

const firstCounter = useSelector(getFirstCounter);

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *