State management is at the heart of every React application. It helps your app track changes in data, user input, and UI updates efficiently. Whether you're handling a simple form or managing complex data structures, understanding how to control and update state is key to building dynamic, responsive UIs.
What is State in React?
There are two types of state in React:
1- Local State: Managed within a single component using useState.
2- Global State: Shared across components using tools like Context API or external libraries (Redux, Zustand, Recoil).
What is useState in React?
// Basic Syntax
const [state, setState] = useState(initialValue);
Example: Simple Counter App
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
const increase = () => setCount(count + 1);
const decrease = () => setCount(count - 1);
return (
<div>
<h2>Counter: {count} </h2>
<button onclick="{increase}">+</button>
<button onclick="{decrease}">-</button>
</div>
);
}
export default Counter;
How it works: Every time you click a button, React updates the state with setCount and re-renders the component.What is useReducer in React?
// Basic Syntax
const [state, dispatch] = useReducer(reducer, initialState);
Example: Complex Counter with Multiple Actions
import React, { useReducer } from 'react';
const initialState = { count: 0 };
function reducer(state, action) {
switch (action.type) {
case 'increment':
return { count: state.count + 1 };
case 'decrement':
return { count: state.count - 1 };
case 'reset':
return { count: 0 };
default:
throw new Error('Unknown action');
}
}
function CounterReducer() {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h2>Count: {state.count}</h2>
<button onclick="{()"> dispatch({ type: 'increment' })}>+</button>
<button onclick="{()"> dispatch({ type: 'decrement' })}>-</button>
<button onclick="{()"> dispatch({ type: 'reset' })}>Reset</button>
</div>
);
}
export default CounterReducer;
Key insight: useReducer centralises state transitions, making complex updates easier to track and debug.
useState vs useReducer
Feature
useState
useReducer
Used For
Simple states
Complex states with multiple transitions
Syntax
Simple and concise
Requires reducer function
State Updates
Direct via setter
Managed via dispatch actions
Best For
Independent values
Interconnected logic
Example: If your state only tracks a counter, use useState. If you're updating nested objects or have multiple state transitions (like async loading, success, error), use useReducer.
Handling Multiple Form Inputs with useReducer
import React, { useReducer } from 'react';
const initialForm = { name: '', email: '', password: '' };
function reducer(state, action) {
return { ...state, [action.field]: action.value };
}
function SignUpForm() {
const [formState, dispatch] = useReducer(reducer, initialForm);
const handleChange = (e) => {
dispatch({ field: e.target.name, value: e.target.value });
};
return (
<form>
<input name="name" value={formState.name} onChange={handleChange} placeholder="Name" />
<input name="email" value={formState.email} onChange={handleChange} placeholder="Email" />
<input name="password" value={formState.password} onChange={handleChange} placeholder="Password" />
<pre>{JSON.stringify(formState, null, 2)}</pre>
</form>
);
}
export default SignUpForm;
This pattern keeps all form state updates controlled and consolidated.
Custom Hook with useReducer
import { useReducer } from 'react';
function useFormReducer(initialState) {
function reducer(state, action) {
return { ...state, [action.name]: action.value };
}
const [form, dispatch] = useReducer(reducer, initialState);
const handleChange = (e) => dispatch(e.target);
return [form, handleChange];
}
export default useFormReducer;
Use this hook across multiple forms without repeating logic.
Combining useState and useReducer
const [isModalOpen, setIsModalOpen] = useState(false);
const [dataState, dispatch] = useReducer(dataReducer, initialData);
When to Use useState vs useReducer
1- State updates are simple and independent.
2- The component controls one or two state variables.
2- The component controls one or two state variables.
Use useReducer when:
1- State transitions are complex (e.g., multiple conditions or nested updates).
2- Multiple values in the state depend on each other.
3- You need predictable and testable state updates.
State management is a critical skill in React development. Use useState for simple, isolated pieces of state and useReducer when logic becomes complex or tightly coupled. By mastering both, you can handle any state scenario efficiently, from a basic counter to scalable applications with multiple data layers.
2- Multiple values in the state depend on each other.
3- You need predictable and testable state updates.
Summary
Tip: Always start simple. Use useState until you feel your logic getting messy, then reach for useReducer!
