crowhill
crowhill

Reputation: 2548

React useReducer and context: how to provide state selectors?

I was never explicitly clear in the original question, but I'm trying to do all of this without Redux, which means no useSelector();

It's an exercise in using just native React to implement selectors.


I'm trying to build a Redux-like arrangement, using hooks, and I'm borrowing heavily from this guy

So, we have a Store:

const CarStore = ({ children }) => {
  const [state, dispatch] = useReducer(
    carReducer,
    [
      {
        type: 'fast',
        mpg: 'bad',
      },
    ],
  );

  return (
    <CarStateContext.Provider value={selectors}>
      <CarDispatchContext.Provider value={dispatch}>
        {children}
      </CarDispatchContext.Provider>
    </CarStateContext.Provider>
  );
};
export const CarStateContext = createContext();
export const CarDispatchContext = createContext();

Now, dispatch automatically has access to state via carReducer, which is very nice. Instant Redux!

Well, half a Redux, anyway. I'm still missing selectors, and that's my question. Given the above context(s), how could I best build out, for example, a selector like getCarType() that would return just 'fast' to a component?

One vague idea would be something like this:

const CarStore = ({ children }) => {
  const [state, dispatch] = useReducer(
    carReducer,
    [
      {
        type: 'fast',
        mpg: 'bad',
      },
    ],
  );

  // selectors, kind of. 
  const selectors = {
    getType: () => { return state.type; },
    getMpg: () => { return state.mpg; },
  };

  return (
    <CarStateContext.Provider value={selectors}> // <- don't return state, return selectors
      <CarDispatchContext.Provider value={dispatch}>
        {children}
      </CarDispatchContext.Provider>
    </CarStateContext.Provider>
  );
};
export const CarStateContext = createContext();
export const CarDispatchContext = createContext();

This does work, I guess, but there's probably a better solution?

Upvotes: 5

Views: 5791

Answers (1)

Shubham Khatri
Shubham Khatri

Reputation: 281656

The best and generic solution here is to provide a simple API called useSelector as provided by redux which takes a callback and calls it with the current state and returns the result.

const CarStore = ({ children }) => {
  const [state, dispatch] = useReducer(
    carReducer,
    [
      {
        type: 'fast',
        mpg: 'bad',
      },
    ],
  );


  const useSelector = (callback) => {
    return callback(state)
  };

  return (
    <CarStateContext.Provider value={{useSelector}}> // <- don't return state, return selectors
      <CarDispatchContext.Provider value={dispatch}>
        {children}
      </CarDispatchContext.Provider>
    </CarStateContext.Provider>
  );
};
export const CarStateContext = createContext();
export const CarDispatchContext = createContext();

Once you do that you can use it an any component like

const Comp = () => {
   const {useSelector} = useContext(CarStateContext);
   const type = useSelector((state) => { return state.type; })
   const mpg = useSelector((state) => { return state.mpg; })

}

EDIT- Approach 2: In the above scenario you need to make use of useContext separately along with useSelector To avoid it you can implement useSelector as a hook too. Also you don't need two separete context for dispatch and state, you can make use of just one

const CarStore = ({ children }) => {
  const [state, dispatch] = useReducer(
    carReducer,
    [
      {
        type: 'fast',
        mpg: 'bad',
      },
    ],
  );


  const getValue = (callback) => {
    return callback(state)
  };

  return (
    <CarStateContext.Provider value={{getValue, dispatch}}> // <- don't return state, return selectors
        {children}
    </CarStateContext.Provider>
  );
};
export const CarStateContext = createContext();

export const useSelector = (callback) => {
   const {getValue} = useContext(CarStateContext);
   return getValue(callback)
}
// Similarly dispatch can be provided like below
export const useDispatch = () => {
   const {dispatch} = useContext(CarStateContext);
   return dispatch
}

and use it in your component like

import { useSelector, useDispatch } from 'path/to/useSelector'
const Comp = () => {
   const type = useSelector((state) => { return state.type; })
   const mpg = useSelector((state) => { return state.mpg; })
   const dispatch = useDispatch();
}

Upvotes: 7

Related Questions