Published
TL;DR - I wrote a custom hook to make useReducer
easier to use. Wanna try it out? It's available on npm.
1npm install --save @dgca/react-use-dispatch-methods 2
Wanna jump to the demo? I got you.
For a long time, I wasn't a huge fan of Redux. The learning curve felt bigger than it needed to be, the switch
statements felt unnatural (I still have to look up the syntax for those since I use them so sparingly), and I just wasn't a fan of the API. Redux Toolkit addressed most of my issues, but the original way of using Redux wasn't my cup of tea.
Because of this, when React introduced hooks, I was a bit apprehensive of useReducer
. I appreciate what it does, but again with the switch
statements, and having to write a lot of the boilerplate yourself.
That's why I decided to wrap useReducer
in a custom hook that implements what I believe is a simpler way of using it. Meet useDispatchMethods
!
So, how does it work?
1const [state, dispatch] = useDispatchMethods( 2 methods, 3 initialState, // optional 4 init, // optional 5 dependencyArray // optional 6); 7
methods (Object<string, function>)
- This is an object where each value is a function that returns the updated state, and each key is the name of the dispatch method you'll use later on. Each function will receive an object of {state, payload}
as its single argument. state
is the current state, and payload
is the argument that was passed to the dispatch method (more on this in a bit).initialState (?*)
- The initial state, same as useState
or useReducer
.init (?function)
- A function to lazily initialize the initial state, same as useReducer
(see: React's docs on this).dependencyArray (?Array<*>)
- In order to prevent recreating some internal objects on subsequent rerenders, useDispatchMethods
uses React's useCallback
and useMemo
. If you need to change the functions in the methods object, you can pass a dependencyArray
and we'll forward those onto useCallback
and useMemo
. Odds are, you won't have to worry about this.state (*)
- The current state, same as useState
or useReducer
.dispatch (Object<string, function>)
- Alright, here's the fun part. Remember the methods
object you passed in earlier? dispatch
is an object that has the same keys. In order to update your state, just call dispatch.someUpdateFunction(optionalPayload)
. When you call a dispatch method (eyy, there's where the hook's name comes from), you're actually dispatching an action and the underlying useReducer
's reducer function figures out which update function it needs to use to modify the state!So, let's see this in action. First, a demo of our custom hook in use. We're using useDispatchMethods
to modify the state that controls the component's background color, and the width of the border. It's ugly, I know.
So, in short, we're defining our update functions by passing an object of functions the first argument. Each function receives an object of {state, payload}
as its argument, and it returns the new state.
The color input shows that you can call a dispatch method with a payload (in this example, it's the input's e.target.value
). That's the payload
that gets passed to the update function. If you don't need to use the payload, you don't have to pass one.
One thing to note is that the update functions must return the entire new state object. In this example, that means cloning the state object by spreading it onto a new object and updating only the property we need to update. You could also use a library like Immer to facilitate this.
1export default function App() { 2 const [state, dispatch] = useDispatchMethods( 3 { 4 setColor: ({ state, payload }) => ({ 5 ...state, 6 color: payload, 7 }), 8 increaseBorderWidth: ({ state }) => ({ 9 ...state, 10 borderWidth: state.borderWidth + 1, 11 }), 12 decreaseBorderWidth: ({ state }) => ({ 13 ...state, 14 borderWidth: state.borderWidth - 1, 15 }), 16 }, 17 { 18 color: "#fff", 19 borderWidth: 10, 20 } 21 ); 22 return ( 23 <div 24 className="App" 25 style={{ 26 backgroundColor: state.color, 27 borderWidth: `${state.borderWidth}px`, 28 }} 29 > 30 <h1> 31 <code>useDispatchMethods</code> demo: 32 </h1> 33 <label> 34 Change background color: 35 <input 36 value={state.color} 37 type="color" 38 onChange={(e) => dispatch.setColor(e.target.value)} 39 /> 40 </label> 41 <br /> 42 <br /> 43 <button onClick={dispatch.increaseBorderWidth}> 44 Increase border width 45 </button> 46 47 <button onClick={dispatch.decreaseBorderWidth}> 48 Decrease border width 49 </button> 50 </div> 51 ); 52} 53
Neat, right? If you're into this, and you want to use it, it's available on npm, or just grab the code from this Gist and paste it into your project.