React useState Hook

Managing state in functional components

Introduction to useState

The useState hook is one of React's most fundamental hooks. It allows functional components to have state variables, which was previously only possible with class components. State in React represents data that changes over time and affects what is rendered on the screen.

Key Points:

  • The useState hook returns an array with two elements: the current state value and a function to update it
  • State updates trigger component re-renders
  • Each state variable is independent and can be updated separately
  • When updating state based on previous state, always use the callback form

Files We Will Work With

George Polya's 4-Step Problem Solving Method

Step 1: Understand the Problem

In this practice, we need to:

  • Create a component that uses the useState hook for two different purposes
  • Implement a theme switcher (light/dark) using state
  • Implement a counter with increment and decrement functionality
  • Ensure our state updates correctly based on previous state

Step 2: Devise a Plan

  1. Import the useState hook from React
  2. Create a state variable for the theme with an initial value of 'light'
  3. Apply the theme class to our component's outer div
  4. Add buttons to switch between light and dark themes
  5. Create a state variable for the counter with an initial value of 0
  6. Display the current count value
  7. Add buttons to increment and decrement the counter
  8. Update the increment and decrement functions to use callback form
  9. (Bonus) Create a toggle theme button

Step 3: Execute the Plan

Let's implement our solution step by step:

Step 1-3: Creating and applying theme state


// Import useState hook and CSS
import { useState } from 'react';
import './UseState.css';

function UseState() {
  // Create theme state with initial value of 'light'
  const [theme, setTheme] = useState('light');
  
  return (
    // Apply the theme class to the outer div
    <div className={`state ${theme}`}>
      <h1>UseState Component</h1>
      {/* We'll add buttons here */}
      <div className="button-container">
        {/* Counter display will go here */}
      </div>
    </div>
  );
}

export default UseState;
                    

Step 4: Adding theme switch buttons


<h1>UseState Component</h1>
<button onClick={() => setTheme('dark')}>Dark</button>
<button onClick={() => setTheme('light')}>Light</button>
<div className="button-container">
                    

Step 5-6: Creating and displaying counter state


function UseState() {
  const [theme, setTheme] = useState('light');
  // Add counter state with initial value of 0
  const [count, setCount] = useState(0);
  
  return (
    <div className={`state ${theme}`}>
      <h1>UseState Component</h1>
      <button onClick={() => setTheme('dark')}>Dark</button>
      <button onClick={() => setTheme('light')}>Light</button>
      <div className="button-container">
        {/* Display the current count */}
        <h2>{count}</h2>
        {/* We'll add increment/decrement buttons here */}
      </div>
    </div>
  );
}
                    

Step 7: Adding counter buttons


<div className="button-container">
  <h2>{count}</h2>
  <button onClick={() => setCount(count + 1)}>Increment</button>
  <button onClick={() => setCount(count - 1)}>Decrement</button>
</div>
                    

Step 8: Updating to use callback form


<div className="button-container">
  <h2>{count}</h2>
  {/* Use callback form for state updates based on previous state */}
  <button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
  <button onClick={() => setCount(prevCount => prevCount - 1)}>Decrement</button>
</div>
                    

Bonus: Toggle theme button


<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
  Toggle Theme
</button>
                    

Step 4: Review the Solution

Let's review our complete solution:

Final UseState.jsx


import { useState } from 'react';
import './UseState.css';

function UseState() {
  // Theme state - controls the background color
  const [theme, setTheme] = useState('light');
  
  // Counter state - controls the displayed number
  const [count, setCount] = useState(0);
  
  return (
    <div className={`state ${theme}`}>
      <h1>UseState Component</h1>
      
      {/* Theme buttons */}
      <button onClick={() => setTheme('dark')}>Dark</button>
      <button onClick={() => setTheme('light')}>Light</button>
      
      {/* Toggle theme button */}
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle Theme
      </button>
      
      <div className="button-container">
        {/* Counter display and buttons */}
        <h2>{count}</h2>
        <button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
        <button onClick={() => setCount(prevCount => prevCount - 1)}>Decrement</button>
      </div>
    </div>
  );
}

export default UseState;
                    

Our solution successfully implements:

  • A theme state that toggles between light and dark
  • A counter state that can be incremented and decremented
  • Proper use of callback functions when updating state based on previous values
  • A bonus toggle theme button that switches between themes

Detailed Explanation

What is useState?

The useState hook is a function that lets you add state to functional components. When called, it returns a pair: the current state value and a function that lets you update it.


const [stateVariable, setStateVariable] = useState(initialValue);
                

Each call to useState creates a separate piece of state. You can call it multiple times to have multiple state variables.

Theme State Explained

Our theme state is a simple string that can be either 'light' or 'dark'. We use this state to apply different CSS classes to our component:


const [theme, setTheme] = useState('light');
// ...
<div className={`state ${theme}`}>
                

When the theme changes, React re-renders the component and applies the new CSS class, changing the background color.

Counter State Explained

Our counter state is a number that starts at 0. We display it directly in our JSX and provide buttons to change it:


const [count, setCount] = useState(0);
// ...
<h2>{count}</h2>
<button onClick={() => setCount(prevCount => prevCount + 1)}>Increment</button>
                

Every time a button is clicked, the state updates and the component re-renders with the new count value.

Why use the callback form?

When updating state based on the previous state value, you should always use the callback form:


// Not ideal when updating based on previous state
setCount(count + 1);

// Better approach
setCount(prevCount => prevCount + 1);
                

This is because React state updates can be batched together for performance reasons. If multiple state updates happen in quick succession, using the variable directly might lead to stale values. The callback form guarantees that you're working with the most current state value.

Think of it like this: If you tell three people to add 1 to a number on a whiteboard, they might all read "5" and write "6", resulting in "6" instead of "8". But if you tell them to "add 1 to whatever number you see", you'll get the correct final value.

Real-World Applications

Theme Switching

Many modern websites and applications offer light and dark modes to improve user experience. Our theme state implementation is a simplified version of this common feature.

In a real application, you might store the user's theme preference in local storage or a database to persist it between sessions.

Form Inputs

The useState hook is commonly used to manage form inputs in React. Each input field typically has its own state variable that updates as the user types.


const [username, setUsername] = useState('');
// ...
<input 
  type="text" 
  value={username} 
  onChange={(e) => setUsername(e.target.value)} 
/>
                

Shopping Cart

Our counter implementation is similar to quantity controls in a shopping cart. In a real application, you might use useState to track quantities of different items.


const [cartItems, setCartItems] = useState([]);

const incrementQuantity = (itemId) => {
  setCartItems(prevItems => 
    prevItems.map(item => 
      item.id === itemId 
        ? {...item, quantity: item.quantity + 1} 
        : item
    )
  );
};
                

Common Pitfalls and Tips

Forgetting Dependencies

When using useState with other hooks like useEffect, make sure to properly list all dependencies to avoid stale state issues.

Direct State Mutation

Never modify state directly. Always use the setter function provided by useState.


// WRONG - direct mutation
count = count + 1;  

// CORRECT - using setter function
setCount(count + 1);
                

Complex State

For complex state (objects, arrays), remember that useState doesn't merge objects like class component's setState does. You need to spread the previous state manually.


const [user, setUser] = useState({ name: '', email: '' });

// Update only the email
setUser(prevUser => ({ ...prevUser, email: 'new@example.com' }));
                

Further Learning

Additional Exercises

  1. Create a form with multiple input fields managed by useState
  2. Implement a to-do list where items can be added, removed, and marked as complete
  3. Create a quiz app that keeps track of the current question and user's score

Related Hooks

After mastering useState, explore these related React hooks:

  • useReducer - For more complex state logic
  • useEffect - For side effects in functional components
  • useContext - For sharing state across components without prop drilling