Reducers in react
Reducers can seem a bit intimidating at first. I was one of those folks who had
written it off as too complicated until recently I got the opportunity to work
on a project that extensively leverages the useReducer
hook and it forced me
to work with it. From my experience, I can say that the hook solves a very
specific problem that is updating and managing complex states and yes it is by
far the perfect tool for it. I’m not going to explain how the hook exactly works
here because the official
documentation is
actually good. Also when you get to it, useReducer
is simple enough that you
can understand it by looking at a simple example. Given below is a small example
of a counter that uses the useReducer
hook to manage it’s state.
import { useReducer } from 'react'
function App() {
const countReducer = (state, action) => {
if (action.type === "add")
return { ...state, count: state.count + state.step }
else if (action.type === "subtract")
return { ...state, count: state.count - state.step }
else if (action.type === "reset")
return { ...state, count: 0 }
else if (action.type === "step")
return { ...state, step: action.step }
}
const [state, dispatch] = useReducer(countReducer, { count: 0, step: 1 });
return (
<>
<h1>{state.count}</h1>
<input
min={1}
name="step"
type="range"
value={state.step}
onChange={e => dispatch({ type: "step", step: Number(e.target.value) })} />
<h3>{state.step}</h3>
<button onClick={() => dispatch({ type: "add" })}>Add</button>
<button onClick={() => dispatch({ type: "subtract" })}>Subtract</button>
<button onClick={() => dispatch({ type: "reset" })}>Reset</button>
</>
)
}
export default App
One could and should try implementing the same logic with useState
first to
understand and appreciate how elegant and efficient useReducer
makes the whole
thing. With useState
you’d need two different states to manage the data and
the step and the logic to deal with all the actions won’t be as elegant either.
Now mind you this is a simple example and in all honesty, could function
just fine with useState
. But if you are dealing with complex states where say
one value is dependent on another or updating one value requires
changing/updating multiple other values, useReducer
will give you a much
better experience than useState
. This drastically helps you reduce the number
of state transitions required, making your application more performant. It also
shines in scenarios where the state is not a number or a string but is a
collection or a hash. useReducer
also enables us to put the business logic
within the hook. This approach is much better than exporting and importing a
bunch of functions all the time as one would do within a context.
All that being said, here are a few things you should look out for while using this hook:
- A huge reducer function that everyone needs to constantly navigate through and refer to in comparison to a scenario where the component and logic or function and function call are close to each other.
- Redundant actions in reducer due to poor maintenance, especially in large teams or in teams where the people are constantly joining and leaving.
- Using
useReducer
where you should be using a context. - Not being conservative about adding actions to the reducer.