user.io
user.io

Reputation: 466

React - reusable Redux hook slice issue (Redux Toolkit, Typescript)

I'm trying to create some hooks to fetch slices of the Redux store so that the store can't access directly to return anything.

If I useSelector directly in the component it works great and doesn't unnecessarily re-render:

*** Modal.tsx ***
const { isModalOpen } = useSelector((state: RootState) => state.carsSlice); // Works great. No unnecessary re-renders

If I create a hook to return the carsSlice then it unnecessarily re-renders the whole page. Why?

E.g.,

*** StateHooks.ts ***
const useGlobalState = () => useSelector((state: RootState) => state);
export const useCarsState = () => useGlobalState().carsSlice;

*** Modal.tsx ***
const { isModalOpen } = useCarsState(); // Re-renders underlying page and is significantly slower than above approach

I'm fetching the specific value from the state so I don't understand why it would re-render the whole page it is being used on? Is there a way to do this?

I've also tried the below but it still causes page re-render:

const useCarsState = useSelector((state: RootState) => state.carsSlice); // Same result

The only way it works as expected is the below BUT I want the custom hooks above:

const { isModalOpen } = useSelector((state: RootState) => state.carsSlice); // Works great

Thanks all.

Upvotes: 2

Views: 920

Answers (2)

markerikson
markerikson

Reputation: 67439

useSelector works by doing reference comparisons of the value you return. If that value changes, it forces the component to re-render.

Because of that, you should never return the entire root state from `useSelector! That will cause the component to always re-render.

It doesn't matter what additional destructuring you do with the returned value later - what matters is what the selector itself returns.

That's why you should always select the smallest piece of state needed by a given component, to ensure that it only re-renders when it absolutely needs to because its data actually changed.

Upvotes: 1

Michal
Michal

Reputation: 71

you can use useShallowEqualSelector It will do only rerender when the value from the store is changed.

Moreover, you can put this into a separate hook like:

import { TypedUseSelectorHook, useSelector, shallowEqual } from "react-redux";
import { RootState } from "store/rootReducer";

const useShallowEqualSelector: TypedUseSelectorHook<RootState> = (selector) =>
  useSelector(selector, shallowEqual);

export default useShallowEqualSelector;

in this case, you are able to use this hook everywhere and you have access to the hints from typescript

Upvotes: 1

Related Questions