Last active
January 7, 2024 12:32
-
-
Save fnky/7d044b94070a35e552f3c139cdf80213 to your computer and use it in GitHub Desktop.
React Hooks: useReducer with actions and selectors (Redux-like)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function useSelectors(reducer, mapStateToSelectors) { | |
const [state] = reducer; | |
const selectors = useMemo(() => mapStateToSelectors(state), [state]); | |
return selectors; | |
} | |
function useActions(reducer, mapDispatchToActions) { | |
const [, dispatch] = reducer; | |
const actions = useMemo(() => mapDispatchToActions(dispatch), [dispatch]); | |
return actions; | |
} | |
const initialState = { count: 0 }; | |
function reducer(state = initialState, action) { | |
switch(action.type) { | |
case 'increment': | |
case 'decrement': | |
return { count: state.count + action.amount }; | |
} | |
} | |
function Example() { | |
const counterReducer = useReducer(reducer, initialState); | |
const { increment, decrement } = useActions(counterReducer, (dispatch) => ({ | |
increment: (amount) => dispatch({ type: 'increment', amount || 1 }), | |
decrement: (amount) => dispatch({ type: 'decrement', amount: -(amount || 1) }) | |
})); | |
const { getCount, getDividedBy } = useSelectors(counterReducer, (state) => ({ | |
getCount: () => state, | |
getDividedBy: (amount) => state.count / amount | |
})); | |
return ( | |
<div> | |
<p>Current count is {getCount()} and divided by two: {getDividedBy(2)}</p> | |
<button onClick={() => increment(1)}>+</button> | |
<button onClick={() => decrement(1)}>-</button> | |
</div> | |
) | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
I have since moved on from this style of state management and gone to libraries like Recoil, jotai and XState. I found that reducers and contexts required a lot of boilerplate, and the fact that managing memoized components is cumbersome and can become hard to debug.
Another issue is that
useContext
currently has no way to select state to prevent unnecessary re-renders out of the box (although they are experimenting with an API to do this). For medium to large projects, which depends on a lot of state, having to implement this myself would be a waste, when most state management libraries already handles this for you.I still use Context for things like dependency injection (e.g. in contrast to deep prop-drilling) and useReducer in small projects which are easy to debug and doesn't need to type of performance that state management libraries provide.
If I were to use Redux, I'd use Redux Toolkit.