Mastering React’s useReducer Hook for State Management

Explore how to effectively use the useReducer hook in React for robust state management. Learn tips and tricks for boosting performance and building scalable apps.

#react
#javascript

When the React team rolled out version 16.8 and brought the hooks paradigm to our community, things started to quickly change in the way we used to write our code.

useState came as a cleaner solution to keep track of our values across rerenders.

useContext has been another game-changer, making context usage way simpler than before.

And especially useReducer revealed itself as a great interface to implement the State reducer pattern…

But is that all it can do? Definitely not.

What React developers coming from an obsessed usage of the state reducer pattern saw, was another API to implement the same pattern, again and again, often limiting their imagination to this single usage, without seeing all the benefits you get from exploiting this hook.

Don’t get me wrong, I also use this pattern with useReducer and it’s extremely powerful. But there is much more we can do.

An example to see…

Check the following implementation of the commonly used useToggle custom hook.

jsx
function useToggle(initialState = false) {
const [state, setState] = useState(initialState);

const toggle = useCallback(() => setState(state => !state), []);

return [state, toggle];
}

It clearly works, no problem! But if you take a closer look, could we maybe refactor this custom hook to use… who knows… fewer hook invocations**?**

What it currently does is create a state with the useState hook, and using its setter function in combination with useCallbackit creates the toggle function. Two hook calls.

What we should know is, as useState does, that also the dispatch function returned by useReducer is guaranteed to be memorized. Using it, we can potentially get rid of the useCallbackcall, still obtaining the same result.

jsx
function useToggle(initialState = false) {
const [state, toggle] = useReducer(state => !state, initialState);

return [state, toggle];
}

// Also, my preference...
const toggler = state => !state;

function useToggle(initialState = false) {
return useReducer(toggler, initialState);
}

Do you see it? useReducer gives us control over the updater function implementation detail, exposing the most powerful API to create custom hooks.

From now on, it’s just about imagination.

I’ve been using useReducer for many purposes, even to abstract the simplest logic to make my code reusable and readable for myself and my colleagues.

I’ll leave you some other examples of how I used it, hopefully, it’ll be of some help to you one day!

useFunctionalRef

jsx
function useFunctionalRef () {
return useReducer((_state, node) => node, null);
}

// Usage
const [inputRef, setInputRef] = useFunctionalRef()

if (shouldRenderInput) return <input ref={setInputRef}>

useInput

jsx
function useInput (initialValue = '') {
return useReducer((value, event) => event.target.value, initialValue);
}

// Usage
const [value, setInputValue] = useInput()

return <input value={value} onChange={setInputValue}>

Conclusion

This piece is intended to give you a push to extend your imagination and make the best use of what the React API exposes to make a composable and readable application.

Hope you enjoyed the piece. Thanks for reading.

Last updated: