How to Manage State in React Using Modern Hooks

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.

Manage State in React Using Modern Hooks

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.

How to Manage State in React Using Modern Hooks

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.

Manage State Using Modern Hooks in React

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:

51 Python Programs

51 PYTHON PROGRAMS PDF FREE

Download a FREE PDF (112 Pages) Containing 51 Useful Python Programs.

pyython developer roadmap

Aspiring to be a Python developer?

Download a FREE PDF on how to become a Python developer.

Let’s be friends

Be the first to know about sales and special discounts.