This content originally appeared on Bits and Pieces - Medium and was authored by Aakash Jha
Easily manage complex local state and logic in React components with useReducer, instead of useState

A world without hooks
Can you imagine how React components would’ve looked like had hooks never been introduced? Hooks made it possible for the developers to step away from traditional class-based components, and still have state and life-cycle, and other functionalities added to them. Despite having a few limitations, hooks undoubtedly make it easy to implement complex logic in a React component while still keeping the code readable, reusable and easy to understand.
Now, a world with hooks
One of the most basic and commonly used hooks is the useState hook which allows functional components to have state variables and setter functions that can be used to update the state. The useState hook takes an initial value as an argument and returns an array with two elements: the current value of the state and a function to update it.
const [stateVariable, setterFunction] = useState(initialValue);
The useReducer( ) hook
The useReducer hook in React allows functional components to manage complex state. It’s often used when the state updates are based on the previous state or when multiple state updates are required.
The useReducer hook takes two arguments:
- A reducer function that specifies how the state should be updated. The reducer function takes the current state and an action as arguments and returns a new state based on the action.
- An initial state, which is the starting value of the state.
The useReducer hook returns an array with two elements: the current state and a dispatch function that can be used to update the state. The dispatch function takes an action as an argument and triggers the reducer function, which calculates the new state based on the action.
In the simplest way, we use the useReducer hook like this:
const [state, dispatch] = useReducer(reducer, initialState);
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
The useReducer hook provides a way to write all the logic for updating the state in a single place, making it easier to understand and maintain compared to multiple useState calls. By using a reducer function, you can write all the logic for updating the state in a single place, making it easier to understand and maintain.
useReducer hook should not be confused with the reducer used in Redux. The React hook is similar to a reducer in Redux in terms of its function, but it’s used for managing the local state of a functional component, while a reducer in Redux is used for managing the global state of an application.
When to use which?
Comparing useState and useReducer hooks
When deciding between the useState and useReducer hooks, you should consider the complexity of the state updates in your application. Let us look at 2 examples, both of which can be achieved by either useState or useReducer hook.
Example 1 — A simple input field.
If the state updates are simple and don’t require multiple updates or updates based on the previous state, then the useState hook may be a better choice.
/**
* App.tsx with a simple textbox, where local state is managed
* with the useState hook
*/
import { ChangeEventHandler } from 'react';
const App = () => {
const [val, setVal] = useState("")
// simple state update for val on every keystroke
const onChangeHandler: ChangeEventHandler<HTMLInputElement> = (e) => {
// some validations
return setVal(e.target.value)
}
return <input type="text" value={value} onChange={onChangeHandler}
}
Example 2: A form with multiple input fields
Say email and password that needs to be updated separately but would also need some validations before storing the value in their respective states.
If the state updates are complex and require multiple updates or updates based on the previous state, then the useReducer hook may be a better choice.
/**
* App.tsx with a form component, consisting of 2 input fields,
* for email and password, where local state is managed
* with the useReducer hook. The reducer function can handle validation
* and other complex logic, without the need of a dedicated onChangeHandler
* and onChange can eaisly be substituted with an inline arrow function.
*/
import { useReducer } from "react";
// list of actions available for this component
enum ACTION_TYPE {
UPDATE_EMAIL = "update_email",
UPDATE_PASSWORD = "update_password",
}
// interface for initial state
interface State {
email: string;
password: string;
}
// interface for action
interface Action {
type: ACTION_TYPE;
payload: string;
}
// value for initial state
const InitialState: State = {
email: "",
password: "",
};
// defining reducer to handle each ACTION_TYPE case
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case ACTION_TYPE.UPDATE_EMAIL:
// some validations
return { ...state, email: action.payload };
case ACTION_TYPE.UPDATE_PASSWORD:
// some validations
return { ...state, password: action.payload };
default:
return state;
}
};
const App= () => {
const [state, dispatch] = useReducer(reducer, InitialState);
return (
<div>
<form className={`${styles.form}`}>
<div className={`${styles.form_group}`}>
<label>Email</label>
<input
type="email"
placeholder="name@example.com"
value={state.email}
onChange={(e) => dispatch({ type: ACTION_TYPE.UPDATE_EMAIL, payload: e.target.value })}
/>
</div>
<div className={`${styles.form_group}`}>
<label>Password</label>
<input
type="password"
placeholder="**********"
value={state.password}
onChange={(e) => dispatch({ type: ACTION_TYPE.UPDATE_PASSWORD, payload: e.target.value })}
/>
</div>
<button>Save</button>
</form>
</div>
);
};
Conclusions
- Looking at both examples, we can say that handling complex state update logic can be done better with the useReducer hook, as compared to useState hook.
- When working with useState hook, any logic required for validations, or any other operation, needs to be handled in the field’s onChangeHandler function.
- The pattern that useReducer follows might look similar to that of reducer in Redux but the two are not the same. The former manages the local state of a functional component while the latter is used to manage the global state of an application.
- Swapping out useState for useReducer: By encapsulating your state update logic in useReducer, you'll be able to handle complex state updates in a centralized and maintainable way, making your React components easier to share across different projects with an open-source toolchain like Bit.
Thanks for stopping by. I hope you found this article interesting, well-structured and helpful.
Make sure to check out my other articles where I talk about React, Node.js and other good dev stuff.
Until next time…
Signing off 🚀
Build Apps with reusable components, just like Lego

Bit’s open-source tool help 250,000+ devs to build apps with components.
Turn any UI, feature, or page into a reusable component — and share it across your applications. It’s easier to collaborate and build faster.
Split apps into components to make app development easier, and enjoy the best experience for the workflows you want:
→ Micro-Frontends
→ Design System
→ Code-Sharing and reuse
→ Monorepo
Learn more
- How We Build Micro Frontends
- How we Build a Component Design System
- How to reuse React components across your projects
- 5 Ways to Build a React Monorepo
- How to Create a Composable React App with Bit
Swapping out useState for useReducer was originally published in Bits and Pieces on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Bits and Pieces - Medium and was authored by Aakash Jha

Aakash Jha | Sciencx (2023-02-08T17:06:07+00:00) Swapping out useState for useReducer. Retrieved from https://www.scien.cx/2023/02/08/swapping-out-usestate-for-usereducer/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.