I’ve been building React applications for over the years, and I remember the days when class components were the only way to handle state.
It used to be quite a headache to manage complex logic, but everything changed when React Hooks were introduced.
In this tutorial, I will show you how to use modern React Hooks to manage state efficiently in your applications.
I have used these methods in dozens of production-level projects, and they are much cleaner than the old ways of doing things.
Why Use Hooks for State Management?
Before Hooks, we had to wrap our logic in class components, which often led to “wrapper hell” and confusing code.
Hooks allow you to use state and other React features without writing a single class. This makes your code more readable, easier to test, and much faster to write.
Method 1: Use the useState Hook for Simple State
The useState hook is the most common way to handle state in a functional component. I use this for simple values like strings, numbers, or booleans.
Let’s look at a practical example: a simple tax calculator that helps users in the USA calculate sales tax for their purchases.
Full Code Example:
import React, { useState } from 'react';
const SalesTaxCalculator = () => {
// Initializing state for the purchase amount
const [amount, setAmount] = useState(0);
const [taxRate, setTaxRate] = useState(6.25); // Default tax for Massachusetts
const calculateTotal = () => {
return (amount + (amount * taxRate) / 100).toFixed(2);
};
return (
<div style={{ padding: '20px', fontFamily: 'Arial' }}>
<h2>USA Sales Tax Calculator</h2>
<label>Purchase Amount ($): </label>
<input
type="number"
value={amount}
onChange={(e) => setAmount(parseFloat(e.target.value) || 0)}
/>
<br /><br />
<label>State Tax Rate (%): </label>
<input
type="number"
value={taxRate}
onChange={(e) => setTaxRate(parseFloat(e.target.value) || 0)}
/>
<h3>Total Price (incl. tax): ${calculateTotal()}</h3>
</div>
);
};
export default SalesTaxCalculator;You can see the output in the screenshot below.

In this code, I used useState twice to track the amount and the tax rate. Whenever the user types in the input box, the state updates and React re-renders the component to show the new total.
Method 2: Handle Complex State with useReducer
When I deal with a state that has multiple sub-values or complex logic, useState can become messy.
In these cases, I prefer using the useReducer hook. It is very similar to Redux but built directly into React.
Let’s build a “401(k) Retirement Contribution Tracker” where we manage multiple related values like annual salary and contribution percentage.
Full Code Example:
import React, { useReducer } from 'react';
// Initial state object
const initialState = {
salary: 75000,
contributionPercent: 5,
employerMatch: 3
};
// Reducer function to handle state changes
function reducer(state, action) {
switch (action.type) {
case 'setSalary':
return { ...state, salary: action.payload };
case 'setContribution':
return { ...state, contributionPercent: action.payload };
case 'reset':
return initialState;
default:
throw new Error();
}
}
const RetirementTracker = () => {
const [state, dispatch] = useReducer(reducer, initialState);
const annualContribution = (state.salary * (state.contributionPercent / 100)).toLocaleString();
return (
<div style={{ padding: '20px', border: '1px solid #ddd' }}>
<h2>401(k) Contribution Tracker (USA)</h2>
<p>Annual Salary:
<input
type="number"
value={state.salary}
onChange={(e) => dispatch({ type: 'setSalary', payload: parseInt(e.target.value) })}
/>
</p>
<p>Your Contribution %:
<input
type="number"
value={state.contributionPercent}
onChange={(e) => dispatch({ type: 'setContribution', payload: parseInt(e.target.value) })}
/>
</p>
<h4>Estimated Annual Personal Contribution: ${annualContribution}</h4>
<button onClick={() => dispatch({ type: 'reset' })}>Reset to Defaults</button>
</div>
);
};
export default RetirementTracker;You can see the output in the screenshot below.

I find this approach much better for scaling features. By using a dispatch function, you centralize how the state is updated, which makes debugging much easier.
Method 3: Share State Globally with useContext
Sometimes, you need to share state across many different components, like a user’s subscription plan.
Passing props down five levels (prop drilling) is a nightmare for any developer. I use the useContext hook to create a “Global Store” without needing external libraries.
Let’s look at a “US Membership Dashboard” example.
Full Code Example:
import React, { useState, createContext, useContext } from 'react';
// 1. Create the Context
const UserContext = createContext();
// 2. Provider Component
export const UserProvider = ({ children }) => {
const [user, setUser] = useState({
name: "Alex Smith",
membership: "Premium",
location: "New York, NY"
});
return (
<UserContext.Provider value={{ user, setUser }}>
{children}
</UserContext.Provider>
);
};
// 3. Child Component using the context
const DashboardHeader = () => {
const { user } = useContext(UserContext);
return (
<header style={{ background: '#f4f4f4', padding: '10px' }}>
Welcome, {user.name} | Status: {user.membership} | Location: {user.location}
</header>
);
};
const UpdateMembership = () => {
const { setUser } = useContext(UserContext);
return (
<button onClick={() => setUser(prev => ({ ...prev, membership: "VIP" }))}>
Upgrade to VIP Member
</button>
);
};
// 4. Main App Component
const App = () => {
return (
<UserProvider>
<div style={{ padding: '20px' }}>
<DashboardHeader />
<h3>Member Settings</h3>
<UpdateMembership />
</div>
</UserProvider>
);
};
export default App;You can see the output in the screenshot below.

Using useContext allows any component inside the UserProvider to access the user data instantly. It’s a lifesaver for themes, user sessions, or localization settings.
Manage State Persistence with useEffect
In many of the apps I’ve built, users expect their data to still be there when they refresh the page.
I use the useEffect hook to sync my React state with localStorage. Here is a quick example of a “Zip Code Preference” saver.
Full Code Example:
import React, { useState, useEffect } from 'react';
const ZipCodeTracker = () => {
// Check localStorage for existing value or default to empty
const [zipCode, setZipCode] = useState(() => {
return localStorage.getItem('userZip') || "";
});
// Update localStorage whenever zipCode changes
useEffect(() => {
localStorage.setItem('userZip', zipCode);
}, [zipCode]);
return (
<div style={{ padding: '20px' }}>
<h2>Set Your Local Weather Area</h2>
<label>Enter US Zip Code: </label>
<input
type="text"
maxLength="5"
value={zipCode}
onChange={(e) => setZipCode(e.target.value)}
placeholder="e.g. 90210"
/>
<p>Your saved Zip Code is: <strong>{zipCode}</strong></p>
<p>(Refresh the page to see it persist!)</p>
</div>
);
};
export default ZipCodeTracker;I always recommend using the functional update pattern inside useState if your initial state depends on an external API or storage.
This ensures the logic only runs once when the component mounts.
Best Practices for State Management
Over the years, I have learned a few hard lessons about the state.
First, keep your state as local as possible.
Don’t put everything in a global context if only one component needs it.
Second, avoid redundant states.
If you have a firstName and lastName state, you don’t need a fullName state; just calculate it during render.
This prevents bugs where one piece of state updates, but the other doesn’t.
In this tutorial, I have covered several ways to manage state in React using Hooks.
I have shown you how to use useState for simple data, useReducer for complex logic, and useContext for global sharing.
Managing state correctly is the key to building fast and maintainable React applications.
You may read:
- Convert React Component to PowerPoint Presentation
- How to Get Props of Component in React
- Create a React ContentEditable Component with Children
- How to Fix React Warning for contentEditable with Children

I am Bijay Kumar, a Microsoft MVP in SharePoint. Apart from SharePoint, I started working on Python, Machine learning, and artificial intelligence for the last 5 years. During this time I got expertise in various Python libraries also like Tkinter, Pandas, NumPy, Turtle, Django, Matplotlib, Tensorflow, Scipy, Scikit-Learn, etc… for various clients in the United States, Canada, the United Kingdom, Australia, New Zealand, etc. Check out my profile.