Search code examples
reduxredux-toolkit

How to access "selectors" from a slice in redux toolkit


So, I'm still new to redux toolkit and I'm likely doing some of this wrong. In any case, I'm curious how to extract my "selectors" from my store/slice in redux.

Let's say I have the following slice:

const mySlice = creatSlice({
    name: 'my-slice',
    initialState: {
      stuff: {
        primary: 'Things',
        secondary: 'Other Things',
      },
    },
    reducers: (create) => ({
      getStuff: create.asyncThunk(
        () => getSomeDataAsynchronously(),
        { ...justAssumeTheseUpdateStateWithData },
      ),
    }), 
    // this is the part I'm most interested in understanding better
    // I'm not even sure I'm doing it right. Is this how to handle
    // using arguments, or do I need to curry or something?
    selectors: {
      selectStuff: (state) => state.stuff,
      selectSpecificStuff: (state, key) => state[key] ?? 'not found',
    }
});

const store = configureStore({
  reducer: mySlice.reducer
});

So here's where I'm most confused. Let's assume I've injected that store in my Provider, and I'm on a child react component:

import { useSelector } from 'react-redux';

const MyComponent = () => {
  const selectSpecificStuff = useSelector(/* how to access from my slice? */);

  return <>{selectSpecificStuff('primary')}</>;
}

Is this even possible? I can't seem to find anything about it in the docs, which makes me wonder what the point of the selectors property is, if you can't access it.

Any assistance is appreciated.


Solution

  • Defining & Accessing the Selectors

    I'm curious how to extract my "selectors" from my store/slice in redux.

    The selectors are extracted just like the generated actions are.

    Example:

    export const {
      selectStuff,
      selectSpecificStuff
    } = myslice.selectors;
    

    The selectSpecificStuff selector is still working with the slice's state, so it would need to access state.stuff first.

    selectors: {
      selectStuff: (state) => state.stuff,
      selectSpecificStuff: (state, key) => state.stuff[key] ?? "not found"
    }
    

    or

    selectors: {
      selectStuff: (state) => state.stuff,
      selectSpecificStuff: (state, key) => {
        const stuff = mySlice.getSelectors().selectStuff(state);
        return stuff[key] ?? "not found";
      }
    }
    

    Using the Selectors

    So here's where I'm most confused. Let's assume I've injected that store in my Provider, and I'm on a child react component:

    import { useSelector } from 'react-redux';
    
    const MyComponent = () => {
      const selectSpecificStuff = useSelector(/* how to access from my slice? */);
    
      return <>{selectSpecificStuff('primary')}</>;
    }
    

    Is this even possible? I can't seem to find anything about it in the docs, which makes me wonder what the point of the selectors property is, if you can't access it.

    The part that initially tripped me up with these selectors is that they are generated expecting to be mounted/injected into the store using their defined reducerPath value, which defaults to the slice's name property by default.

    Example:

    import { createSlice } from '@reduxjs/toolkit';
    
    const mySlice = createSlice({
      name: 'my-slice', // <-- default reducer path
      initialState: {
        stuff: {
          primary: 'Things',
          secondary: 'Other Things',
        },
      },
      reducers: (create) => ({
        ....
      }), 
      selectors: {
        selectStuff: (state) => state.stuff,
        selectSpecificStuff: (state, key) => state.stuff[key] ?? "not found"
      }
    });
    
    export const {
      selectStuff,
      selectSpecificStuff
    } = myslice.selectors;
    
    ...
    
    const store = configureStore({
      reducer: {
        "my-slice": mySlice.reducer // <-- mount under correct path
      }
    });
    

    enter image description here

    Import the selectors, and in the useSelector hook callback pass the root state and the key argument to the slice selector.

    import { useSelector } from 'react-redux';
    import { selectStuff, selectSpecificStuff } from '../path/to/mySlice';
    
    const MyComponent = () => {
      const stuff = useSelector(selectStuff);
      const primaryStuff = useSelector((state) =>
        selectSpecificStuff(state, "primary")
      );
      const secondaryStuff = useSelector((state) =>
        selectSpecificStuff(state, "secondary")
      );
      const tertiaryStuff = useSelector((state) =>
        selectSpecificStuff(state, "tertiary")
      );
    
      console.log({
        stuff:          // { stuff: { primary: "Things", secondary: "Other Things" } }
        primaryStuff,   // "Things"
        secondaryStuff, // "Other Things"
        tertiaryStuff,  // "not found"
      });
    
      ...
    }
    

    enter image description here

    Alternative Selector Usage

    getSelectors

    Using mySlice.getSelectors() is the other way to access the selectors.

    const store = configureStore({
      reducer: mySlice.reducer
    });
    
    import { useSelector } from 'react-redux';
    import mySlice from '../path/to/mySlice';
    
    const MyComponent = () => {
      const stuff = useSelector(mySlice.getSelectors().selectStuff);
      const primaryStuff = useSelector((state) =>
        mySlice.getSelectors().selectSpecificStuff(state, "primary")
      );
      const secondaryStuff = useSelector((state) =>
        mySlice.getSelectors().selectSpecificStuff(state, "secondary")
      );
      const tertiaryStuff = useSelector((state) =>
        mySlice.getSelectors().selectSpecificStuff(state, "tertiary")
      );
    
      console.log({
        stuff:          // { stuff: { primary: "Things", secondary: "Other Things" } }
        primaryStuff,   // "Things"
        secondaryStuff, // "Other Things"
        tertiaryStuff,  // "not found"
      });
    
      ...
    }