Menu

Updating State in React: Best Practices & Examples

Updating State the Right Way

The Two Rules

State updates cause two classes of bug for beginners. Both have simple rules.

Rule 1: State updates are not instant

jsx

function handleClick() {
  setCount(count + 1);
  console.log(count);   // Still shows the OLD value
}

React schedules the update and applies it before the next render. Reading the variable immediately after calling the setter gives you the old value.

This matters when you update the same state twice:

jsx

// Wrong — both lines use the same old count, so it only increases by 1
setCount(count + 1);
setCount(count + 1);

// Correct — the functional form uses the latest value each time
setCount(prevCount => prevCount + 1);
setCount(prevCount => prevCount + 1);

The functional form passing a function instead of a value is the safe choice whenever the new state depends on the old state.

Rule 2: Never modify objects or arrays directly

React compares the old and new state to decide whether to re-render. If you change an existing object, the old and new references are the same, so React sees no change.

jsx

const [student, setStudent] = useState({ name: "Priya", progress: 40 });

// Wrong — React does not detect this
student.progress = 60;
setStudent(student);

// Correct — create a new object
setStudent({ ...student, progress: 60 });

The ...student spread copies all existing properties, and progress: 60 overwrites one.

Arrays Follow the Same Rule

jsx

const [tasks, setTasks] = useState(["Watch lesson 1", "Solve exercise 1"]);

// Adding — create a new array
setTasks([...tasks, "Watch lesson 2"]);

// Removing — filter returns a new array
setTasks(tasks.filter(task => task !== "Solve exercise 1"));

// Updating one item — map returns a new array
setTasks(tasks.map(task => task === "Watch lesson 1" ? "Watch lesson 1 (done)" : task));

Avoid push(), pop(), and splice() on state arrays — they modify the original. Use spread, filter(), and map(), which return new arrays.

Modifies the original (avoid)

Returns a new array (use)

push(), unshift()[...array, item]
pop(), splice()filter()
direct index assignmentmap()
sort(), reverse()[...array].sort()

Example

src/components/TaskList.jsx

jsx

import { useState } from "react";

function TaskList() {
  const [tasks, setTasks] = useState([
    { id: 1, text: "Complete JSX lesson", done: true },
    { id: 2, text: "Build a course card", done: false }
  ]);

  function toggleTask(id) {
    setTasks(tasks.map(task =>
      task.id === id ? { ...task, done: !task.done } : task
    ));
  }

  return (
    <ul>
      {tasks.map(task => (
        <li key={task.id}>
          <span>{task.done ? "✓" : "○"} {task.text}</span>
          <button onClick={() => toggleTask(task.id)}>Toggle</button>
        </li>
      ))}
    </ul>
  );
}

export default TaskList;

How It Works

  • tasks.map(...) builds a brand-new array.
  • For the matching task, { ...task, done: !task.done } creates a new object with the flag flipped.
  • Every other task is returned unchanged.
  • React sees a new array reference and re-renders.

ReactJS Tutorial



Learn via Video Course

React.js for Beginners: Build Modern Web Applications with React Logo

React.js for Beginners: Build Modern Web Applications with React

6 hrs



Get in Touch For Details