React useSelector hook Is it better to spread object or specify only needed fields

Viewed 3444

If I have a huge object say (40+ fields) and I call useSelector in a component but only need 3 fields of such object is it better for performance to just call 3 fields like this:

const user = useSelector((state) => ({
    id: state.userReducer.id,
    name: state.userReducer.name,
    lastName: state.userReducer.lastName
  }));

In this case its okayish because we only need 3 fields, but if we need 10+ fields things can get quite verbose. In which case a spread operator saves me a lot of code but is it a bad practice? due to obfuscation of code, performance or some other reasons?

const user = useSelector((state) => ({ ...state.userReducer }));
1 Answers

You should be doing it like that for performance reasons:

const user = useSelector(state => state.userReducer);

Your selector will be run whenever an action is dispatched. If it returns something different then on the last run it will force the component to re-render. By spreading into a new object it will always return a new object. Therefore your component will re-render on every action that gets dispatched.

From the react-redux docs:

useSelector() will also subscribe to the Redux store, and run your selector whenever an action is dispatched.

[...]

  • 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.

  • [...]

  • useSelector() uses strict === reference equality checks by default, not shallow equality

Also I find the key userReducer to be a little awkward. It is just the user not the reducer of the user. A reducer is a pure function that turns actions into changes not an object of data.

EDIT:

As noted by @Adam in the comment if you want to further reduce the amount of re-renders you can use a memorized selector to select only the state you are interested in. A good library to create such a selector is reselect.

// example

import { createSelector } from 'reselect'; 

const basicUserDataSelector = createSelector(
    ({user}) => user.id,
    ({user}) => user.name,
    ({user}) => user.lastName,
    (id, name, lastName) => ({id, name, lastName})
);

// usage

const user = useSelector(basicUserDataSelector);

In your case the easiest way to only select the state needed ist to tell useSelector to do a shallow equal comparison instead of a reference equality comparison. useSelector accepts a comparator function as the second argument:

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

const user = useSelector(
    ({user}) => ({
        id: user.id
        name: user.name
        lastName: user.lastName
    }),
    shallowEqual
);

It is best practice to only take as few data as needed to render from the store to limit the amount of re-rendering. The extra cost of selecting more data then actually needed depends on the component and the frequency of changes on the selected data.

Related