javascripttypescriptreduxredux-toolkit

@reduxjs/toolkit - createSelector with parameters


I have a ReactJS app with Redux and a couple of createSelector but some of them I would like to change.

Here are examples of the current selectors:

export const getAppData = createSelector(
  (state: RootState): AppDataState => state[`appData-${getUniquishModuleId()}`],
  (appData: AppDataState): AppDataState => appData,
);
export const getNextPageToRender = createSelector(
  [
    (state: RootState): PageSlug[] =>
      state[`appData-${getUniquishModuleId()}`].pagesToRender,
    (_: RootState, currentPageSlug: PageSlug): PageSlug => currentPageSlug,
  ],
  (pagesToRender: PageSlug[], currentPageSlug: PageSlug): PageSlug => {
    const currentPageIndex: number = pagesToRender.findIndex(
      (page: PageSlug) => page === currentPageSlug
    );
    return pagesToRender[currentPageIndex + 1];
  },
);

Now what I would like to do is that getNextPageToRender also makes usage of the getAppData selector.

I have tried:

export const getNextPageToRender = createSelector(
  getAppData,
  [
    (appData: AppDataState): PageSlug[] =>
      getContextRelatedAppData(appData).pagesToRender,
    (_: AppDataState, currentPageSlug: PageSlug): PageSlug => currentPageSlug,
  ],
  (pagesToRender: PageSlug[], currentPageSlug: PageSlug): PageSlug => {
    const currentPageIndex: number = pagesToRender.findIndex(
      (page: PageSlug) => page === currentPageSlug
    );
    return pagesToRender[currentPageIndex + 1];
  },
);

And I also tried:

export const getNextPageToRender = createSelector(
  [
    getAppData,
    (appData: AppDataState): PageSlug[] =>
      getContextRelatedAppData(appData).pagesToRender,
    (_: AppDataState, currentPageSlug: PageSlug): PageSlug => currentPageSlug,
  ],
  (pagesToRender: PageSlug[], currentPageSlug: PageSlug): PageSlug => {
    const currentPageIndex: number = pagesToRender.findIndex(
      (page: PageSlug) => page === currentPageSlug
    );
    return pagesToRender[currentPageIndex + 1];
  },
);

But this syntax does not seem to work.

Can someone help? What am I doing wrong?


Solution

  • The first argument to createSelector is an array of input selector functions, and the second argument is the output selector function where the function args are the results of the input selectors.

    Replace the first input selector function with getAppData, and access the appropriate pagesToRender property.

    Using createSelector and declaring an output function that returns the input, i.e. value => value, is anti-pattern. See Identity Function Check for details.

    Selecting the appData is a simple selector. It is also strongly suggested to use a consistent naming convention when creating selector functions, e.g. using a select- prefix.

    export const selectAppData = (state: RootState): AppDataState =>
      state[`appData-${getUniquishModuleId()}`];
    

    Create an intermediate selector that intakes the selectAppData selector and outputs the pagesToRender value.

    const selectPagesToRender = createSelector(
      [selectAppData],
      (appData): PageSlug[] => appData.pagesToRender
    );
    
    export const selectNextPageToRender = createSelector(
      [
        selectPagesToRender, // <-- add new input selector
        (_: RootState, currentPageSlug: PageSlug): PageSlug => currentPageSlug,
      ],
      (
        pagesToRender,
        currentPageSlug
      ): PageSlug => {
        const currentPageIndex: number = pagesToRender.findIndex(
          (page: PageSlug) => page === currentPageSlug
        );
        return pagesToRender[currentPageIndex + 1];
      },
    );