Simplifying React Hooks: useReducer
Introduction When managing complex states in React, useState often falls short. This is where useReducer comes in, providing a more structured way to handle state transitions. In this article, we’ll explain useReducer, provide TypeScript-based examples to ensure type safety and clarity and discuss its benefits. What is useReducer? useReducer is a React Hook that provides an alternative to useState. It is useful for predictably handling state logic, especially in complex scenarios where multiple state updates depend on each other. Syntax: const [state, dispatch] = useReducer(reducer, initialState); state: The current state value. dispatch: A function used to trigger state changes. reducer: A function that takes the current state and an action, then returns a new state. initialState: The default state when the component mounts. Why Use useReducer Instead of useState? Better state management: Suitable for complex state logic with multiple transitions. Predictability: State transitions are centralized in a reducer function. Improved debugging: Actions make state changes more traceable. Implementing useReducer in TypeScript Let’s implement a simple counter with useReducer. Step 1: Define State and Actions type State = { count: number; }; type Action = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" }; Step 2: Create the Reducer Function const reducer = (state: State, action: Action): State => { switch (action.type) { case "INCREMENT": return { count: state.count + 1 }; case "DECREMENT": return { count: state.count - 1 }; case "RESET": return { count: 0 }; default: return state; } }; Step 3: Use useReducer in a Component import { useReducer } from "react"; const Counter = () => { const [state, dispatch] = useReducer(reducer, { count: 0 }); return ( Count: {state.count} dispatch({ type: "INCREMENT" })}>Increment dispatch({ type: "DECREMENT" })}>Decrement dispatch({ type: "RESET" })}>Reset ); }; export default Counter; Handling Complex State When dealing with more complex states, useReducer scales better than useState. For example, managing a form’s state: Define State and Actions type FormState = { username: string; email: string; }; type FormAction = | { type: "CHANGE_USERNAME"; payload: string } | { type: "CHANGE_EMAIL"; payload: string } | { type: "RESET" }; Create the Reducer Function const formReducer = (state: FormState, action: FormAction): FormState => { switch (action.type) { case "CHANGE_USERNAME": return { ...state, username: action.payload }; case "CHANGE_EMAIL": return { ...state, email: action.payload }; case "RESET": return { username: "", email: "" }; default: return state; } }; Use in a Component const Form = () => { const [state, dispatch] = useReducer(formReducer, { username: "", email: "" }); return ( dispatch({ type: "CHANGE_USERNAME", payload: e.target.value })} placeholder="Username" /> dispatch({ type: "CHANGE_EMAIL", payload: e.target.value })} placeholder="Email" /> dispatch({ type: "RESET" })}>Reset Username: {state.username} Email: {state.email} ); }; export default Form; When Should You Use useReducer? When state logic is complex and interdependent. When the next state depends on the previous one. When you need more predictable state transitions. When managing state in large-scale applications. Conclusion useReducer is a powerful alternative to useState, especially for managing complex state logic. Using it with TypeScript enhances type safety and maintainability. By structuring state updates through actions and a reducer function, useReducer makes React applications more scalable and predictable.

Introduction
When managing complex states in React, useState
often falls short. This is where useReducer
comes in, providing a more structured way to handle state transitions.
In this article, we’ll explain useReducer, provide TypeScript-based examples to ensure type safety and clarity and discuss its benefits.
What is useReducer?
useReducer
is a React Hook that provides an alternative to useState
. It is useful for predictably handling state logic, especially in complex scenarios where multiple state updates depend on each other.
Syntax:
const [state, dispatch] = useReducer(reducer, initialState);
state
: The current state value.dispatch
: A function used to trigger state changes.reducer
: A function that takes the current state and an action, then returns a new state.initialState
: The default state when the component mounts.
Why Use useReducer Instead of useState?
Better state management: Suitable for complex state logic with multiple transitions.
Predictability: State transitions are centralized in a reducer function.
Improved debugging: Actions make state changes more traceable.
Implementing useReducer in TypeScript
Let’s implement a simple counter with useReducer
.
Step 1: Define State and Actions
type State = {
count: number;
};
type Action = { type: "INCREMENT" } | { type: "DECREMENT" } | { type: "RESET" };
Step 2: Create the Reducer Function
const reducer = (state: State, action: Action): State => {
switch (action.type) {
case "INCREMENT":
return { count: state.count + 1 };
case "DECREMENT":
return { count: state.count - 1 };
case "RESET":
return { count: 0 };
default:
return state;
}
};
Step 3: Use useReducer in a Component
import { useReducer } from "react";
const Counter = () => {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<h2>Count: {state.count}h2>
<button onClick={() => dispatch({ type: "INCREMENT" })}>Incrementbutton>
<button onClick={() => dispatch({ type: "DECREMENT" })}>Decrementbutton>
<button onClick={() => dispatch({ type: "RESET" })}>Resetbutton>
div>
);
};
export default Counter;
Handling Complex State
When dealing with more complex states, useReducer
scales better than useState
. For example, managing a form’s state:
Define State and Actions
type FormState = {
username: string;
email: string;
};
type FormAction =
| { type: "CHANGE_USERNAME"; payload: string }
| { type: "CHANGE_EMAIL"; payload: string }
| { type: "RESET" };
Create the Reducer Function
const formReducer = (state: FormState, action: FormAction): FormState => {
switch (action.type) {
case "CHANGE_USERNAME":
return { ...state, username: action.payload };
case "CHANGE_EMAIL":
return { ...state, email: action.payload };
case "RESET":
return { username: "", email: "" };
default:
return state;
}
};
Use in a Component
const Form = () => {
const [state, dispatch] = useReducer(formReducer, { username: "", email: "" });
return (
<div>
<input
type="text"
value={state.username}
onChange={(e) => dispatch({ type: "CHANGE_USERNAME", payload: e.target.value })}
placeholder="Username"
/>
<input
type="email"
value={state.email}
onChange={(e) => dispatch({ type: "CHANGE_EMAIL", payload: e.target.value })}
placeholder="Email"
/>
<button onClick={() => dispatch({ type: "RESET" })}>Resetbutton>
<p>Username: {state.username}p>
<p>Email: {state.email}p>
div>
);
};
export default Form;
When Should You Use useReducer?
When state logic is complex and interdependent.
When the next state depends on the previous one.
When you need more predictable state transitions.
When managing state in large-scale applications.
Conclusion
useReducer
is a powerful alternative to useState
, especially for managing complex state logic.
Using it with TypeScript enhances type safety and maintainability. By structuring state updates through actions and a reducer function, useReducer
makes React applications more scalable and predictable.