State and the useState Hook

State is one of the most important concepts in React. While props allow data to be passed into a component from the outside, state is data that a component manages internally. When a component's state changes, React automatically re-renders that component to show the updated information.

Think of state as a component's memory. A counter remembers its current count. A form remembers what has been typed. A toggle switch remembers whether it is on or off. All of these are examples of state.

Introducing the useState Hook

In functional components, state is managed using the useState Hook. A Hook is a special function provided by React that adds functionality to functional components. useState is the most commonly used Hook.

To use useState, it must first be imported from React:


import { useState } from 'react';

How useState Works

useState is called with an initial value and returns an array with two items:

  1. The current state value
  2. A function to update that state value

const [count, setCount] = useState(0);

In this example:

  • count — the current value of the state (starts at 0)
  • setCount — the function used to update the value
  • 0 — the initial state value (the starting value when the component first loads)

The naming convention is to name the state variable and its setter function as a pair: [value, setValue]. This is a common pattern in React development.

A Simple Counter Example


import { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Current count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
      <button onClick={() => setCount(count - 1)}>Decrease</button>
      <button onClick={() => setCount(0)}>Reset</button>
    </div>
  );
}

When the "Increase" button is clicked, setCount(count + 1) is called. React updates the state and re-renders the component, displaying the new count value. The UI is always in sync with the state.

State is Specific to Each Component Instance

Each instance of a component has its own independent state. If the same Counter component is used twice on a page, each counter tracks its own count separately:


function App() {
  return (
    <div>
      <Counter />
      <Counter />
    </div>
  );
}

Clicking "Increase" on the first counter does not affect the second counter. They are completely independent.

State With Strings

State can hold any type of value — not just numbers. Here is an example using a string:


import { useState } from 'react';

function NameDisplay() {
  const [name, setName] = useState("Guest");

  return (
    <div>
      <p>Hello, {name}!</p>
      <button onClick={() => setName("Alice")}>Switch to Alice</button>
      <button onClick={() => setName("Bob")}>Switch to Bob</button>
    </div>
  );
}

When a button is clicked, the name in state is updated, and the greeting automatically reflects the new name.

State With Booleans — Toggle Example


import { useState } from 'react';

function LightSwitch() {
  const [isOn, setIsOn] = useState(false);

  return (
    <div>
      <p>The light is {isOn ? "ON" : "OFF"}.</p>
      <button onClick={() => setIsOn(!isOn)}>Toggle Light</button>
    </div>
  );
}

The ! operator flips the boolean value. Each time the button is clicked, isOn switches between true and false.

State With Objects

State can also hold an object. When updating object state, the entire object must be spread and modified — never mutate the original state directly:


import { useState } from 'react';

function UserForm() {
  const [user, setUser] = useState({ name: "Alice", age: 25 });

  function incrementAge() {
    setUser({ ...user, age: user.age + 1 });
  }

  return (
    <div>
      <p>Name: {user.name}</p>
      <p>Age: {user.age}</p>
      <button onClick={incrementAge}>Increase Age</button>
    </div>
  );
}

The spread operator { ...user } copies all existing properties, and then age: user.age + 1 overrides just the age. This keeps the rest of the object intact.

Why Not Modify State Directly?

Directly modifying a state variable (e.g., count = count + 1) without using the setter function will not trigger a re-render. React only detects state changes when the setter function is used. Direct mutations bypass React's tracking system and lead to bugs where the UI does not reflect the actual data.

Using the Previous State Value

When the new state depends on the previous state, a function should be passed to the setter instead of a direct value. This ensures the update is based on the most recent state:


// ✅ Safer approach — using previous state
<button onClick={() => setCount(prevCount => prevCount + 1)}>
  Increase
</button>

This is especially important when state updates might happen multiple times in quick succession, such as during rapid clicking.

Key Points

  • State is a component's internal memory — it holds data that can change over time.
  • The useState Hook is used to add state to functional components.
  • useState returns the current state value and a setter function as an array.
  • When state is updated via the setter function, React re-renders the component.
  • Each component instance has its own independent state.
  • State can hold any JavaScript value — numbers, strings, booleans, arrays, or objects.
  • Never modify state directly — always use the setter function provided by useState.

The next topic covers Event Handling in React — how to respond to user actions like clicks, keystrokes, and form submissions.

Leave a Comment

Your email address will not be published. Required fields are marked *