Search code examples
reactjsreduxreact-redux

Too many re-renders with useSelector hook closure


Considering this state, I need to select some data from it:

const initialState: PlacesStateT = {
    activeTicket: null,
    routes: {
        departure: {
            carriageType: 'idle',
            extras: {
                wifi_price: 0,
                linens_price: 0,
            },
        },
        arrival: {
            carriageType: 'idle',
            extras: {
                wifi_price: 0,
                linens_price: 0,
            },
        },
    },
};

so, I came up with two approaches:

first:

const useCoaches = (dir: string) => {
    const name = mapDirToRoot(dir);
    const carType = useAppSelector((state) => state.places.routes[name].carriageType);

    const infoT = useAppSelector((state) => {
        return state.places.activeTicket.trainsInfo.find((info) => {
            return info.routeName === name;
        });
    });

    const coaches = infoT.trainInfo.seatsTrainInfo.filter((coach) => {
        return coach.coach.class_type === carType;
    });

    return coaches;
};

and second:

const handlerActiveCoaches = (name: string) => (state: RootState) => {
    const { carriageType } = state.places.routes[name];
    const { activeTicket } = state.places;

    const trainInfo = activeTicket.trainsInfo.find((info) => {
        return info.routeName === name;
    });

    return trainInfo.trainInfo.seatsTrainInfo.filter((coach) => {
        return coach.coach.class_type === carriageType;
    });
};

const useActiveInfo = (dir: string) => {
    const routeName = mapDirToRoot(dir);
    const selectActiveCoaches = handlerActiveCoaches(routeName);
    const coaches = useAppSelector(selectActiveCoaches);

    return coaches;
};

Eventually, if the first one works ok then the second one gives a lot of useless re-renders in component. I suspect that there are problems with selectActiveCoaches closure, maybe react considers that this selector is different on every re-render but I am wrong maybe. Could you explain how does it work?


Solution

  • selectActiveCoaches finishes with return seatsTrainInfo.filter(). This always returns a new array reference, and useSelector will force your component to re-render whenever your selector returns a different reference than last time. So, you are forcing your component to re-render after every dispatched action:

    https://react-redux.js.org/api/hooks#equality-comparisons-and-updates

    One option here would be to rewrite this as a memoized selector with Reselect:

    https://redux.js.org/usage/deriving-data-selectors