React state change causes React to render/reconcile, but the important part is: how does React know that the state actually changed?

That’s where references come in.

The statement:

“React state updates rely heavily on creating new references rather than mutating existing state.”

does not mean:

“Every state update creates a new DOM element.”

Those are completely different things.

1. Start with a primitive

This is straightforward:

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

React compares the old state and new state:

old state = 0
new state = 1
0 !== 1

So React knows the state changed and can schedule an update.

No reference issue here because numbers are primitive values.


2. Now consider an object

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

Suppose you do this:

user.name = "John";
setUser(user);

You changed the object.

But notice:

old user ───────┐
│
v
same object
^
│
new user ───────┘

Both old user and new user point to the same object reference.

Conceptually:

Object.is(oldUser, newUser);
// true

So React can see:

“The value passed to the state setter is the same value/reference I already have.”

React can therefore skip the update because from its perspective, the state value hasn’t changed.


3. That’s why we create a new object

Instead:

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

Now:

old user ──────> Object A
new user ──────> Object B

Even though most of the data is the same:

Object A !== Object B

So React can easily detect that the state value is different.

Object.is(oldUser, newUser);
// false

Then React schedules the update.


4. Important: React is NOT looking at every property

This is the part that usually causes confusion.

Suppose:

const oldUser = {
name: "Abhilash",
age: 40
};
const newUser = {
name: "John",
age: 40
};

React doesn’t need to deeply compare:

name changed?
age changed?
address changed?
...

It can first see:

oldUser === newUser

which is:

false

That gives React a cheap signal that something changed.

This is one reason immutable updates are useful.


5. But the component re-render and reference are two different things

This is exactly where your question comes from.

You said:

“If state changes the component re-renders, not needed a new element/reference.”

The correction is:

A state update may cause a render, but React still needs to determine whether the state actually changed and what parts of the UI need updating.

Think of it as two separate stages.

setState(...)
↓
Did the state value change?
↓
Render/reconcile
↓
What UI actually changed?
↓
Update DOM where necessary

The reference is relevant to the first part and also to several optimization mechanisms.

The DOM elements are a different matter.


6. New object reference does NOT mean new DOM element

Suppose:

function User({ user }) {
return <h1>{user.name}</h1>;
}

You do:

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

React doesn’t necessarily throw away:

<h1>Abhilash</h1>

and create a completely new <h1>.

React will reconcile the new render with the previous render and determine that the text changed:

Before:
<h1>Abhilash</h1>
After:
<h1>John</h1>

It can update only the text node/DOM property that changed.

So:

New JS object reference
≠
New DOM element

That’s the key distinction.


7. Arrays are the same

Suppose:

const [users, setUsers] = useState([
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
]);

Don’t do:

users.push({ id: 3, name: "John" });
setUsers(users);

You’re modifying the existing array.

The reference is still the same:

old users ──────> Array A
↑
new users ──────> Array A

Instead:

setUsers([
...users,
{ id: 3, name: "John" }
]);

Now:

old users ──────> Array A
new users ──────> Array B

React can detect that the state value is different.


8. This is also why React.memo cares about references

This becomes even more important when you have:

const UserCard = React.memo(function UserCard({ user }) {
return <h2>{user.name}</h2>;
});

React.memo can skip rendering if props are considered unchanged.

For object props, reference identity matters.

<UserCard user={user} />

If the parent creates:

const newUser = { ...user };

then:

newUser !== user

So the child sees a changed prop.

This is one reason React applications care about referential equality.


9. useEffect dependencies also use this idea

This is another place where your earlier questions connect.

Suppose:

useEffect(() => {
console.log("user changed");
}, [user]);

React tracks the dependency:

previous user reference
vs
current user reference

If the reference changes:

oldUser !== newUser

the effect can run again.

But if you mutate:

user.name = "John";
setUser(user);

the reference is still the same.

That’s why mutating state directly creates confusing bugs.


10. Functions are references too

This is also why we discussed useCallback.

Every render can create a new function:

const handleClick = () => {
console.log("hello");
};

Even though the function code looks identical, each render can produce a new function object:

Render 1:
handleClick → Function A
Render 2:
handleClick → Function B

So:

oldHandleClick === newHandleClick
// false

useCallback can preserve the same function reference when appropriate.

That’s another example of React relying on reference identity.


11. The bigger picture

So when people say React relies heavily on references, they are talking about efficient change detection, not DOM creation.

                 React State
                     |
         +-----------+-----------+
         |                       |
     primitive              object/array
         |                       |
    value comparison        reference comparison
         |                       |
         +-----------+-----------+
                     |
              State changed?
                     |
                     v
                  Render
                     |
                     v
               Reconciliation
                     |
                     v
        Minimal DOM updates

The rule you should remember

For objects and arrays:

// ❌ Mutate existing state
user.name = "John";
setUser(user);
// ✅ Create a new object
setUser({
...user,
name: "John"
});

And:

// ❌
users.push(newUser);
setUsers(users);
// ✅
setUsers([...users, newUser]);

The reason is not “React needs a new DOM element.”

The reason is:

React uses identity/reference comparisons extensively to detect changes efficiently. Creating a new object/array gives React a new reference that represents the new state.

One nuance: React’s state setter uses Object.is-style equality to determine whether the new state is identical, and in that case React can skip a re-render. This is why mutating an object and passing the same reference back is particularly problematic.