Reputation: 2548
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
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