React hooks useState and useReducer are equivalent in theoretical expressiveness

Which do you prefer?

Daishi Kato
ITNEXT
Published in
3 min readJun 30, 2019

--

Introduction

useReducer is a powerful hook. It’s known that useState is implemented with useReducer.

In the React hooks docs, it’s noted like this:

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one. useReducer also lets you optimize performance for components that trigger deep updates because you can pass dispatch down instead of callbacks.

For a long time, I misunderstood that useReducer is more powerful than useState and there’s some optimization that can’t be achieved by useState.

It turns out that useState is as powerful as useReducder in terms of expressiveness. This is because useState allows functional updates. Even with deep updates, you can pass down custom callbacks.

So, whether you useState or useReducer is just your preference. I would useReducer when I used dispatch in JSX. Having logic outside of JSX seems clean to me.

Example

If you create a custom hook, whether you useState or useReducer is just about an internal implementation issue. Let’s look at an example. We implement a simple counter example with two hooks. For both cases, hooks return action callbacks, which is important to hide implementation details in this comparison.

useReducer

const initialState = { count1: 0, count2: 0 };const reducer = (state, action) => {
switch (action.type) {
case 'setCount1':
if (state.count1 === action.value) return state; // to bail out
return { ...state, count1: action.value };
case 'setCount2':
if (state.count2 === action.value) return state; // to bail out
return { ...state, count2: action.value };
default:
throw new Error('unknown action type');
}
};
const useCounter = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const setCount1 = useCallback(value => {
dispatch({ type: 'setCount1', value });
}, []);
const setCount2 = useCallback(value

--

--