Home > Software engineering >  Is there a performance difference between using useSelector once or multiple times?
Is there a performance difference between using useSelector once or multiple times?

Time:10-30

I'm basically wondering if there is a difference in performance, or any other pros and cons, between the following 2 code snippets. Are there any objective reasons to use one over the other, or is it just personal preference?

const prop1 = useSelector((state) => state.appModel.prop1);
const prop2 = useSelector((state) => state.appModel.prop2);
const prop3 = useSelector((state) => state.appModel.prop3);
const prop4 = useSelector((state) => state.appModel.prop4);
const {
  prop1,
  prop2,
  prop3,
  prop4,
} = useSelector((state) => ({
  prop1: state.appModel.prop1,
  prop2: state.appModel.prop2,
  prop3: state.appModel.prop3,
  prop4: state.appModel.prop4,
}));

The second option instinctively feels like it might be more performant, because it only uses useSelector once, but then I wonder if one property changing may cause more re-renders because they're all grouped together.

UPDATE:

Also wondering if this even shorter version has any pros and cons?

const { prop1, prop2, prop3, prop4 } = useSelector((state) => state.appModel);

Apologies if this is a duplicate, I tried searching but wasn't entirely sure of the terminology to search for, and couldn't see any matching examples.

CodePudding user response:

The individual one is much better performance wise. Your second approach creates a new object every time the selector is called, so when useSelector compares the before and after it always looks like it has changed. As a result, your component is forced to render on every change to the redux store, even if nothing happened to prop1-prop4.

If you want to do #2, you need to make write a memoized selector, so that it returns the same object if none of the individual values have changed. You can read more about memoized selectors here: https://redux.js.org/usage/deriving-data-selectors#optimizing-selectors-with-memoization

(While it's good to learn about memoized selectors, for your case i would recommend sticking with approach #1)

Also wondering if this even shorter version has any pros and cons?

const { prop1, prop2, prop3, prop4 } = useSelector((state) => state.appModel);

That's better than #2, because now it will only need to rerender when appModel changes. If the only things in appModel are prop1-prop4, then this will be basically the same as #1. If there are extra properties in appModel that you don't care about, then this may result in a few extra renders when appModel changes due to those other properties.

CodePudding user response:

If prioritizing performance efficiency from top to bottom, I think it would be like this:

  1. const prop1 = useSelector((state) => state.appModel.prop1); ...
  2. const { prop1, ... } = useSelector((state) => ({ prop1: state.appModel.prop1, ... })
  3. const { prop1,... } = useSelector((state) => state.appModel);

Always try to return primitive values, such as string, number, boolean, etc. when it is possible from callback of useSelector (or use memoizing techniques, see below). Because when you return an object value (object, array, array-like object, etc.) from callback function of useSelector, it causes a new re-rendering of that component, no matter whether any state was changed in your redux tree related to those states that you returned inside of every time new object or not. So, in cases 2 and 3 you are returning a value of object type (assuming prop1, ... is not object type value), therefore your component's props changes always and it re-renders it self by nature of React, even if rendering happens in the parent component of that component.

So, you need somehow memoize your object values to achieve better app performance, which helps to avoid unnecessary re-renderings. So, to do it you can pass shallowEqual function as a second paratmeter to useSelector from react-redux library. Of course, you can write your custom function to compare as well.

import { shallowEqual, useSelector } from 'react-redux'

const selectedData = useSelector(selectorReturningObject, shallowEqual)

Regarding the your use case, you can read more right below (including other memoize options to reference to the same object value, when component re-calls useSelector):

You may call useSelector() multiple times within a single function component. Each call to useSelector() creates an individual subscription to the Redux store. Because of the React update batching behavior used in React Redux v7, a dispatched action that causes multiple useSelector()s in the same component to return new values should only result in a single re-render.

With useSelector(), returning a new object every time will always force a re-render by default. If you want to retrieve multiple values from the store, you can:

  • Call useSelector() multiple times, with each call returning a single field value
  • Use Reselect or a similar library to create a memoized selector that returns multiple values in one object, but only returns a new object when one of the values has changed.
  • Use the shallowEqual function from React-Redux as the equalityFn argument to useSelector()

Equality Comparisons and Updates in React-Redux

  • Related