Ticker

6/recent/ticker-posts

🔄 React State – How Components Remember and React to Changes

🔄 React State

😵 The Problem

Let's say you're building a simple counter.

You want this:

Count: 0

Then the user clicks a button:

Count: 1

Click again:

Count: 2

Simple, right?

So you might try:

function Counter() {
  let count = 0;

  function increment() {
    count++;
  }

  return (
    <div>
      <h2>{count}</h2>
      <button onClick={increment}>
        +1
      </button>
    </div>
  );
}

Looks reasonable.

But click the button…

Nothing happens. 😅

The variable changes.

The UI doesn't.

And this is where React State becomes important.

💡 The Solution: State

State is data that a React component needs to remember between renders and react to when it changes.

The most common way to create state is with useState.

import { useState } from "react";

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

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

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

Now when setCount() runs:

state changes
     ↓
React schedules a re-render
     ↓
component runs again
     ↓
UI reflects the new state

That's the part that makes React different from simply changing a JavaScript variable.

🧠 State Is Not Just a Variable

This distinction is important.

Normal JavaScript:

let count = 0;

count++;

console.log(count);

The variable changes.

But React doesn't automatically know that it needs to update the screen.

With state:

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

setCount(count + 1);

You're telling React:

"This value changed. Please update the UI."

That's the real purpose of state.

🔥 State Can Be Anything

State isn't limited to numbers.

You can store strings:

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

Booleans:

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

Arrays:

const [todos, setTodos] = useState([]);

Objects:

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

The type isn't the important part.

The important question is:

Does this value change over time and should the UI respond to that change?

If yes, state might be the right tool.

🎯 Real Example: Toggle Button

A very common UI problem:

"When I click this button, something should appear or disappear."

That's state.

import { useState } from "react";

function Profile() {
  const [showDetails, setShowDetails] = useState(false);

  return (
    <div>
      <button onClick={() => setShowDetails(!showDetails)}>
        {showDetails ? "Hide Details" : "Show Details"}
      </button>

      {showDetails && (
        <p>
          Frontend Developer with 5 years of experience.
        </p>
      )}
    </div>
  );
}

The state controls the UI.

showDetails = false
        ↓
details hidden

showDetails = true
        ↓
details visible

This pattern is everywhere in React:

  • dropdowns
  • modals
  • tabs
  • menus
  • accordions
  • dark mode
  • loading states

🔥 Updating Objects in State

Here's where beginners often get into trouble.

Suppose you have:

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

Don't do this:

user.age = 26; // ❌

You're directly mutating the existing object.

Instead:

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

You're creating a new object with the updated value.

This matters because React state should generally be treated as immutable.

📦 Updating Arrays in State

Same idea.

Don't do:

todos.push(newTodo); // ❌

Instead:

setTodos([
  ...todos,
  newTodo
]);

Removing an item:

setTodos(
  todos.filter(todo => todo.id !== id)
);

Updating an item:

setTodos(
  todos.map(todo =>
    todo.id === id
      ? { ...todo, completed: true }
      : todo
  )
);

The pattern is simple:

Create a new array/object instead of directly modifying the existing one.

⚡ The Functional Update Pattern

Here's a subtle issue that becomes important in real applications.

You might write:

setCount(count + 1);

That's perfectly fine for many situations.

But when the new state depends on the previous state, the functional form is often safer:

setCount(prevCount => prevCount + 1);

For example:

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

  function incrementTwice() {
    setCount(prev => prev + 1);
    setCount(prev => prev + 1);
  }

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

Now each update works from the latest state value.

You'll see this pattern a lot in production React code.

🧠 State Updates Are Not Immediate

This is another common source of confusion.

setCount(count + 1);

console.log(count);

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

But that's not how React state works.

The state value inside the current render doesn't suddenly change halfway through your function.

React schedules an update and then renders the component again with the new state.

A useful mental model is:

Current render
      ↓
setState()
      ↓
React schedules update
      ↓
New render
      ↓
New state value

Once you understand this, a lot of "why is my state one step behind?" bugs start making sense.

💡 Real Developer Insight

Here's the state mistake I see most often:

Putting everything into state.

For example:

const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Doe");

const [fullName, setFullName] = useState("John Doe");

Do you really need fullName as separate state?

Probably not.

You can derive it:

const fullName = `${firstName} ${lastName}`;

That's called derived data.

If a value can be calculated from existing state or props, you usually don't need another state variable for it.

Otherwise you create two sources of truth.

And sooner or later:

firstName → updated
lastName  → updated
fullName  → forgot to update 💀

That's a bug waiting to happen.

🏗️ State Ownership Matters

This is where React architecture starts getting interesting.

Imagine:

App
 ├── Header
 ├── Sidebar
 └── Dashboard
      ├── Search
      └── ProductList

Where should the search state live?

If only Search needs it:

function Search() {
  const [query, setQuery] = useState("");
}

Perfect.

But if both Search and ProductList need the query?

The state probably belongs higher up:

function Dashboard() {
  const [query, setQuery] = useState("");

  return (
    <>
      <Search
        query={query}
        setQuery={setQuery}
      />

      <ProductList query={query} />
    </>
  );
}

This is called lifting state up.

The general rule:

Keep state as close as possible to the components that need it, but high enough that all required components can access it.

That's a much better approach than putting everything in global state.

⚠️ Common Developer Mistakes

1. Changing State Directly

user.name = "Alex"; // ❌

Use:

setUser({
  ...user,
  name: "Alex"
});

2. Using State for Everything

Not every variable needs state.

If changing a value doesn't require a UI update, a normal variable or another tool may be more appropriate.

3. Creating Duplicate State

Avoid storing values that can already be calculated from props or existing state.

const fullName = `${firstName} ${lastName}`;

is usually better than maintaining another fullName state.

4. Forgetting Functional Updates

If your update depends on the previous value:

setCount(prev => prev + 1);

is usually the safer pattern.

5. Putting State Too High

Just because state can live in App doesn't mean it should.

Keep local state local when possible.

It makes components easier to understand and maintain.

🚀 Best Practice Summary

✅ Use state for values that change over time and affect the UI
✅ Treat objects and arrays in state as immutable
✅ Use functional updates when the next value depends on the previous value
✅ Don't store values that can be derived from existing state or props
✅ Keep state as close as possible to the components that actually need it
Reactions

Post a Comment

0 Comments