React State

A React application needs to remember information that can change while the user interacts with it.

For example, a counter needs to remember its current number. A form needs to remember what the user has typed. A menu may need to remember whether it is open or closed. A shopping cart needs to remember which products have been added.

React uses state to store this kind of changing information.

State is one of the most important concepts in React. Once you understand state, many React features become much easier to understand because state is closely connected to components, events, rendering, forms, conditional rendering, lists, and hooks.

A simple state example looks like this:

import { useState } from "react";

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

  return (
    <div>
      <p>Count: {count}</p>

      <button onClick={() => setCount(count + 1)}>
        Increase
      </button>
    </div>
  );
}

export default Counter;

Here, count is the current state value, while setCount is the function used to update it.

When the user clicks the button, the state changes and React renders the component again with the new value.

The basic idea is:

User Action
     ↓
State Update
     ↓
React Re-renders
     ↓
Updated UI

This chapter explains what state is, how it differs from normal variables, why state updates cause re-rendering, why React treats state as a snapshot, and how to update state correctly.

What Is State?

State is information that a React component remembers and can update over time.

State is useful when a value can change because of user interaction or other application activity and that change should be reflected in the UI.

Examples include:

  • Counter values
  • Input values
  • Selected options
  • Open or closed menus
  • Login status
  • Shopping cart items
  • Form data
  • Loading status
  • Error status
  • Active tabs
  • Search text
  • Likes and favorites

For example, a counter needs to remember its current value.

import { useState } from "react";

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

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

The value:

count

is state.

The function:

setCount

updates that state.

The initial value is:

0

because we wrote:

useState(0)

The useState Hook

React provides the useState Hook for adding state to function components.

The basic syntax is:

const [state, setState] = useState(initialValue);

For example:

const [name, setName] = useState("");

This creates two values:

name
↓
Current state value

setName
↓
Function used to update the state

Another example:

const [age, setAge] = useState(25);

Here:

age = 25

initially.

You can update it with:

setAge(26);

Why Does useState Return Two Values?

The array returned by useState contains:

  1. The current state value.
  2. A state setter function.

For example:

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

You can think of it as:

count     → current value
setCount  → update function

The names are not required to follow this exact pattern, but using a meaningful name followed by set is a common convention.

For example:

const [isOpen, setIsOpen] = useState(false);
const [email, setEmail] = useState("");
const [products, setProducts] = useState([]);

State vs Variables

One of the most important things beginners need to understand is that React state is different from a normal JavaScript variable.

Consider a normal variable:

function Counter() {
  let count = 0;

  function increase() {
    count = count + 1;
    console.log(count);
  }

  return (
    <button onClick={increase}>
      Increase
    </button>
  );
}

The JavaScript variable can change.

However, changing it does not tell React that the UI needs to be updated.

If you want a changed value to cause the component to render the new value, state is the appropriate mechanism.

import { useState } from "react";

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

  function increase() {
    setCount(count + 1);
  }

  return (
    <button onClick={increase}>
      Count: {count}
    </button>
  );
}

Now React knows that the state has changed.

The Important Difference

A normal variable:

let count = 0;

is simply a JavaScript variable.

React does not track it as component state.

State:

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

is managed by React.

When you call:

setCount(...)

React schedules the component to render again.

Comparing Variables and State

Normal VariableReact State
Managed by JavaScriptManaged by React
Can change during executionUpdated through a setter
Changing it does not request a React re-renderUpdating state causes React to render with the new state
Value does not persist as component state across rendersState is preserved between renders
Useful for temporary calculationsUseful for data that affects the UI

This does not mean normal variables are useless.

You should still use normal variables for temporary calculations and values that do not need to persist between renders.

For example:

function Price({ price, quantity }) {
  const total = price * quantity;

  return <p>Total: ${total}</p>;
}

total does not need to be state because it is calculated from the current props.

When Should You Use State?

A useful question is:

Does this value need to be remembered between renders and does changing it affect the UI?

If the answer is yes, state may be appropriate.

For example, an input value:

const [email, setEmail] = useState("");

The application needs to remember what the user typed.

A menu’s visibility:

const [isOpen, setIsOpen] = useState(false);

The application needs to remember whether the menu is open.

A selected tab:

const [activeTab, setActiveTab] = useState("overview");

The application needs to remember which tab is selected.

Don’t Put Everything in State

Not every value needs to be state.

For example:

const firstName = "John";
const lastName = "Smith";
const fullName = `${firstName} ${lastName}`;

If fullName can simply be calculated from other values, you generally do not need separate state for it.

Instead:

function User({ firstName, lastName }) {
  const fullName = `${firstName} ${lastName}`;

  return <h2>{fullName}</h2>;
}

Keeping unnecessary derived values out of state can make components simpler.

State and Re-rendering

One of the most important properties of React state is that updating it causes React to render the component again.

Consider:

import { useState } from "react";

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

  function increase() {
    setCount(count + 1);
  }

  return (
    <div>
      <h2>{count}</h2>

      <button onClick={increase}>
        Increase
      </button>
    </div>
  );
}

export default Counter;

Initially:

count = 0

React renders:

0

The user clicks the button.

This runs:

setCount(count + 1);

The state becomes 1.

React then renders the component again.

The UI becomes:

1

Another click:

2

and so on.

What Is a Re-render?

A re-render means React calls the component again to determine what the UI should look like with the current state and props.

For example:

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

  console.log("Component rendered");

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

When the component initially renders, the message appears in the console.

When state changes, React renders the component again.

It is important to understand that a re-render does not mean React necessarily throws away and recreates the entire DOM page. React compares the new result with the previous UI and applies the necessary DOM updates.

State Update Flow

A typical state update follows this pattern:

Initial State
     ↓
Component Renders
     ↓
User Interacts
     ↓
Event Handler Runs
     ↓
State Setter Called
     ↓
React Schedules Update
     ↓
Component Renders Again
     ↓
UI Reflects New State

For example:

function App() {
  const [isOpen, setIsOpen] = useState(false);

  function toggleMenu() {
    setIsOpen(!isOpen);
  }

  return (
    <div>
      <button onClick={toggleMenu}>
        Menu
      </button>

      {isOpen && (
        <nav>
          <a href="/">Home</a>
          <a href="/about">About</a>
        </nav>
      )}
    </div>
  );
}

The state determines whether the navigation is rendered.

State Updates

You update state using the setter function returned by useState.

For example:

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

Update it with:

setCount(10);

The next render will use:

count = 10

Updating String State

const [name, setName] = useState("");

setName("Karim");

Updating Number State

const [age, setAge] = useState(25);

setAge(26);

Updating Boolean State

const [isOpen, setIsOpen] = useState(false);

setIsOpen(true);

Updating Array State

const [items, setItems] = useState([]);

setItems(["Laptop", "Mouse"]);

Updating Object State

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

To update the object safely, preserve the properties you want to keep:

setUser({
  ...user,
  age: 26
});

More details about updating objects and arrays will be covered in later state chapters.

State Updates Are Not Immediately Reflected in the Current Code

A common beginner mistake is expecting the state variable to change immediately after calling its setter.

Consider:

function handleClick() {
  setCount(count + 1);

  console.log(count);
}

You might expect the console to immediately show the new value.

But count in that running event handler still refers to the value from the current render.

React schedules the update, and the component will render again with the new state.

For example:

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

function handleClick() {
  setCount(count + 1);

  console.log(count);
}

If count is 0, the console can still print:

0

The next render will have:

count = 1

This behavior becomes much easier to understand once you learn the idea of state as a snapshot.

State as a Snapshot

React state behaves like a snapshot for a particular render.

Consider:

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

  function handleClick() {
    setCount(count + 1);

    console.log(count);
  }

  return (
    <button onClick={handleClick}>
      {count}
    </button>
  );
}

Suppose the current render has:

count = 0

When the button is clicked:

setCount(count + 1);

requests a new state value of 1.

But the count variable inside the currently running handleClick function still represents the snapshot from that render:

count = 0

React then renders the component again with:

count = 1

The important idea is:

State does not change the value inside the already-running render. It requests a new render with a new state value.

Visualizing a Snapshot

Think of each render as taking a picture.

First render:

Snapshot 1

count = 0

After the state update:

Snapshot 2

count = 1

The first snapshot does not change into the second snapshot. React creates a new render using the updated state.

Multiple State Updates

The snapshot concept becomes especially important when multiple state updates happen in the same event handler.

Consider:

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

  function handleClick() {
    setCount(count + 1);
    setCount(count + 1);
    setCount(count + 1);
  }

  return (
    <button onClick={handleClick}>
      {count}
    </button>
  );
}

If count is 0 in this render, each expression uses:

count = 0

So all three calls request:

1

They do not automatically become:

1 → 2 → 3

because the count variable does not change during that event handler.

If you want each update to be based on the previous state value, use a functional state update.

function handleClick() {
  setCount((currentCount) => currentCount + 1);
  setCount((currentCount) => currentCount + 1);
  setCount((currentCount) => currentCount + 1);
}

Now each update can use the latest queued state value.

The result is:

0 → 1 → 2 → 3

This pattern is extremely important when performing multiple updates based on previous state.

Functional State Updates

A functional state update looks like this:

setCount((currentCount) => currentCount + 1);

Instead of providing the next value directly, you provide a function.

React passes the previous state value to that function.

For example:

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

setCount((currentCount) => currentCount + 1);

If the current state is 0, the result is 1.

If the current state is 10, the result is 11.

When Should You Use Functional Updates?

Use functional updates when the new state depends on the previous state.

For example:

setCount((currentCount) => currentCount + 1);

For toggling:

setIsOpen((currentValue) => !currentValue);

For adding an item:

setItems((currentItems) => [
  ...currentItems,
  newItem
]);

For removing an item:

setItems((currentItems) =>
  currentItems.filter((item) => item.id !== id)
);

This approach makes the relationship between old and new state explicit.

Updating State from an Event

Events and state are often used together.

For example:

import { useState } from "react";

function LikeButton() {
  const [liked, setLiked] = useState(false);

  function handleClick() {
    setLiked((currentLiked) => !currentLiked);
  }

  return (
    <button onClick={handleClick}>
      {liked ? "Liked" : "Like"}
    </button>
  );
}

export default LikeButton;

The user clicks the button.

The event handler updates state.

React renders the component again.

The button text changes.

Like
 ↓
Click
 ↓
State changes
 ↓
Liked

Updating State from Input

Controlled inputs are another common use of state.

import { useState } from "react";

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

  function handleChange(event) {
    setName(event.target.value);
  }

  return (
    <div>
      <input
        value={name}
        onChange={handleChange}
        placeholder="Enter your name"
      />

      <p>Your name is: {name}</p>
    </div>
  );
}

export default NameForm;

The input value is stored in state.

Every change updates the state.

React then renders the latest value.

This pattern is fundamental to React forms.

State Updates and Batching

React may batch multiple state updates together so that several updates can be processed efficiently.

For example:

function handleClick() {
  setFirstName("John");
  setLastName("Smith");
  setAge(25);
}

React can process these updates together and perform a resulting render rather than unnecessarily rendering after every single setter call.

This is one reason you should not think of a state setter as an instruction to immediately change the variable on the current render.

Instead, think of it as:

Request that React use this new state for a future render.

State Updates and Re-rendering

When state changes, React determines whether the component needs to render with the new state.

For example:

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

Then:

setCount(1);

React receives the update and renders using the new state.

The component function runs again:

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

  // ...
}

You don’t manually call the component function.

React handles the rendering process.

State Can Have Different Data Types

State does not have to be a number.

You can store many types of data.

String

const [name, setName] = useState("");

Number

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

Boolean

const [isOpen, setIsOpen] = useState(false);

Array

const [items, setItems] = useState([]);

Object

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

Null

const [selectedUser, setSelectedUser] = useState(null);

The appropriate initial value depends on what the state represents.

Choosing a Good Initial State

Your initial state should represent the natural starting condition.

For example, a closed menu:

const [isOpen, setIsOpen] = useState(false);

An empty search field:

const [search, setSearch] = useState("");

An empty shopping cart:

const [cart, setCart] = useState([]);

No selected product:

const [selectedProduct, setSelectedProduct] = useState(null);

Choosing sensible initial values makes the rest of your component logic easier to understand.

Don’t Mutate State Directly

React state should generally be treated as read-only.

Do not directly modify an array or object stored in state.

For example, avoid:

items.push(newItem);

Instead:

setItems((currentItems) => [
  ...currentItems,
  newItem
]);

For objects, avoid:

user.name = "Sarah";

Instead:

setUser((currentUser) => ({
  ...currentUser,
  name: "Sarah"
}));

The new value should be created and passed to the state setter.

Later chapters will cover arrays and objects in state in much greater detail.

State vs Derived Values

Sometimes beginners create state for values that can simply be calculated.

For example:

function Cart({ items }) {
  const total = items.reduce(
    (sum, item) => sum + item.price,
    0
  );

  return <p>Total: ${total}</p>;
}

There is no need to create:

const [total, setTotal] = useState(0);

if the total can always be calculated from items.

A useful rule is:

If a value can be calculated from existing props or state during rendering, you often don’t need separate state for it.

This avoids having multiple pieces of state that can become inconsistent.

A Complete State Example

Let’s combine state, events, conditional rendering, and list rendering.

import { useState } from "react";

function ShoppingList() {
  const [items, setItems] = useState([
    "Laptop",
    "Keyboard"
  ]);

  const [input, setInput] = useState("");

  function handleChange(event) {
    setInput(event.target.value);
  }

  function handleAdd() {
    if (input.trim() === "") {
      return;
    }

    setItems((currentItems) => [
      ...currentItems,
      input
    ]);

    setInput("");
  }

  return (
    <div>
      <h1>Shopping List</h1>

      <input
        value={input}
        onChange={handleChange}
        placeholder="Enter an item"
      />

      <button onClick={handleAdd}>
        Add Item
      </button>

      {items.length === 0 ? (
        <p>Your list is empty.</p>
      ) : (
        <ul>
          {items.map((item, index) => (
            <li key={`${item}-${index}`}>
              {item}
            </li>
          ))}
        </ul>
      )}
    </div>
  );
}

export default ShoppingList;

This component uses state for two things:

items
↓
Shopping list

input
↓
Current input value

When the user types:

onChange
   ↓
setInput()
   ↓
input state changes
   ↓
UI updates

When the user clicks Add:

onClick
   ↓
setItems()
   ↓
items state changes
   ↓
UI updates

The example uses an index combined with the item text only to keep the demo simple. For a real dynamic list, each item should ideally have a stable unique ID.

Image Placement: Understanding React State

Place this image immediately after the introduction.

Create a modern 16:9 educational infographic explaining React state:

State
  ↓
Stores changing information
  ↓
User interacts
  ↓
State Setter
  ↓
React Re-renders
  ↓
Updated UI

Include simple examples such as:

  • Counter
  • Input field
  • Toggle button
  • Shopping cart

Use a modern Web4Learners visual style with clean typography, rounded cards, subtle gradients, modern developer UI elements, and minimal text. Make it look contemporary rather than like an old programming diagram.

Image Placement: State vs Variables

Place this image after the “State vs Variables” section.

Create a clean 16:9 comparison infographic.

Left side:

Normal Variable

let count = 0
     ↓
Value changes
     ↓
React is not automatically notified

Right side:

React State

useState()
     ↓
setCount()
     ↓
React updates UI

Use a modern developer-focused design with rounded cards, subtle gradients, clean typography, and minimal text.

Image Placement: State as a Snapshot

Place this image immediately after the “State as a Snapshot” section.

Create a modern 16:9 educational visualization showing:

Render 1
count = 0
     ↓
User clicks
     ↓
setCount(1)
     ↓
Render 2
count = 1

Visually show that the first render’s count remains 0 while React creates a new render with count = 1.

Use a modern React/developer aesthetic, clear arrows, rounded cards, subtle gradients, and minimal explanatory text.

Common State Mistakes

Changing a Normal Variable Instead of State

Incorrect:

let count = 0;

function increase() {
  count++;
}

If the UI depends on count, use state:

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

Updating State Directly

Avoid:

user.name = "John";

Use:

setUser((currentUser) => ({
  ...currentUser,
  name: "John"
}));

Expecting the State Variable to Change Immediately

Avoid assuming this:

setCount(count + 1);

console.log(count);

will print the new value inside the same running handler.

The current render’s state remains the same snapshot.

Using the Current Value for Multiple Dependent Updates

Instead of:

setCount(count + 1);
setCount(count + 1);

use:

setCount((currentCount) => currentCount + 1);
setCount((currentCount) => currentCount + 1);

when you need multiple updates based on the previous state.

Storing Derived Values Unnecessarily

Avoid creating state for values that can simply be calculated.

Instead of maintaining separate state for a cart total, calculate it from the cart items when appropriate.

Putting Too Much Data into One State Object

A large state object can sometimes make updates unnecessarily complicated.

For example:

const [state, setState] = useState({
  name: "",
  email: "",
  theme: "light",
  isOpen: false
});

Separate state variables may be clearer when these values represent unrelated pieces of state:

const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [theme, setTheme] = useState("light");
const [isOpen, setIsOpen] = useState(false);

The right structure depends on how the values are related and updated.

Best Practices for React State

Use State for Data That Affects the UI

If changing a value should cause the interface to update, state may be appropriate.

Use Descriptive State Names

Prefer:

const [isLoading, setIsLoading] = useState(false);

over:

const [x, setX] = useState(false);

Good names make code easier to understand.

Use Functional Updates When Based on Previous State

Prefer:

setCount((currentCount) => currentCount + 1);

when the new value depends on the old value.

Treat State as Immutable

Do not directly mutate objects or arrays stored in state.

Create a new value and pass it to the setter.

Keep State Minimal

Do not store values in state simply because they exist.

Ask:

Can I calculate this value from existing props or state?

If yes, separate state may not be necessary.

Keep Related State Together

Values that always change together may sometimes make sense in the same state object.

For example:

const [form, setForm] = useState({
  name: "",
  email: ""
});

But unrelated values can often be kept separately.

Let React Manage Rendering

Do not manually manipulate the DOM to update UI that React can render from state.

Instead of manually changing an element’s text, update state and let React determine the UI.

Real-World Web4Learners Example

Imagine Web4Learners has a tutorial search interface.

The application might need to remember:

const [searchTerm, setSearchTerm] = useState("");
const [isLoading, setIsLoading] = useState(false);
const [results, setResults] = useState([]);
const [error, setError] = useState(null);

Each piece of state represents a different part of the application’s changing UI.

When the user types:

React Hooks

the input state changes.

When a search begins:

isLoading = true

When results arrive:

isLoading = false
results = [...]

If the request fails:

error = "Unable to load tutorials"

The UI can then respond to these state values.

Conceptually:

User types
    ↓
searchTerm updates
    ↓
Search starts
    ↓
isLoading updates
    ↓
API responds
    ↓
results or error updates
    ↓
React re-renders
    ↓
New UI appears

This is how state becomes the foundation of interactive applications.

Practice Exercise

Build a React Counter Dashboard.

Your application should include:

  • A counter
  • An Increase button
  • A Decrease button
  • A Reset button
  • A toggle button
  • A text input

Use state for each value that needs to affect the UI.

Requirements

The counter should start at:

0

Increase should add:

+1

Decrease should subtract:

-1

Reset should return the counter to:

0

The toggle should switch between:

ON

and:

OFF

The input should display the text as the user types.

Bonus Challenge

Create a button that increases the counter by three using three state updates.

Try both:

setCount(count + 1);
setCount(count + 1);
setCount(count + 1);

and:

setCount((currentCount) => currentCount + 1);
setCount((currentCount) => currentCount + 1);
setCount((currentCount) => currentCount + 1);

Observe the difference and use the second approach when each update needs to build on the previous state.

State Cheat Sheet

ConceptExamplePurpose
Create stateuseState(0)Add state to a component
Read statecountGet current value
Update statesetCount(1)Set a new value
Functional updatesetCount(c => c + 1)Update from previous value
String stateuseState("")Store text
Number stateuseState(0)Store numbers
Boolean stateuseState(false)Store true/false
Array stateuseState([])Store collections
Object stateuseState({})Store structured data
TogglesetOpen(open => !open)Toggle boolean state

Basic State

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

Update State

setCount(10);

Update from Previous State

setCount((currentCount) => currentCount + 1);

Boolean Toggle

setIsOpen((currentValue) => !currentValue);

Array Update

setItems((currentItems) => [
  ...currentItems,
  newItem
]);

Object Update

setUser((currentUser) => ({
  ...currentUser,
  name: "John"
}));

Key Takeaways

State is one of the core concepts of React.

Remember these important points:

  • State stores information that a component needs to remember between renders.
  • State is commonly created with the useState Hook.
  • useState() returns the current state value and a setter function.
  • Updating state tells React that the component needs to render with the new state.
  • State is different from ordinary JavaScript variables.
  • Changing a normal variable does not automatically cause React to update the UI.
  • A re-render means React runs the component again to determine the UI for the current state and props.
  • State behaves like a snapshot for each render.
  • Calling a state setter does not change the state variable inside the already-running render.
  • When the next state depends on the previous state, use a functional update.
  • Multiple state updates can be batched.
  • State can contain strings, numbers, booleans, arrays, objects, null, and other appropriate values.
  • Do not directly mutate arrays or objects stored in state.
  • Use the state setter to provide a new value.
  • Avoid storing values in state when they can simply be calculated from existing props or state.
  • Use descriptive state names.
  • Keep state organized according to how the values are related.
  • State works closely with events, conditional rendering, forms, lists, and component communication.

The most important React state pattern to remember is:

State
  ↓
User Action
  ↓
State Setter
  ↓
New State
  ↓
React Re-render
  ↓
Updated UI

Once you understand this cycle, React becomes much easier to reason about. State is the mechanism that allows a component to remember changing information and reflect those changes in its UI.

Leave a Comment