ghostty
michael@vegas : ~/writing · (main)
● connected
mdabout.md tsxprojects/ logexperience.log md2026-05-react-immutable-state.md jsoncontact.json
← :writing
2026-05 ⏱ 4 min

React & Immutable State

Mutate state directly and React refuses to re-render. The virtual DOM, reference equality, and why Immer exists — explained without the JS spec jargon.

MP
Michael Pope Senior AI Engineer · Fractional CTO

2025-10-08 15:31

Status:

Tags: react.js javascript functional programing

Under The Hood

Imagine we have the following state object in React :

const [person, setPerson] = useState({ name: "Michael", occupation: "software engineer" });

The typical way to update state in React would be by calling the setPerson function like this:

function updatePerson() {
	setPerson((prevPerson) =>{  ...prevPerson, name: "John" }) // Their name is John now!
}

If you're curious, you may have wondered, wait, why can't I just update the state property without called the setPerson function? Seems like less hasle...

function badUpdatePerson() {
	person.name = "John" // Incorrect way to tru and update state in React
}

The primary reason this is a bad idea, is you would expect your code to display this newly updated name in the UI as John, not Michael. Modifying state in this way will not trigger a state update, and you will still see Michael.

What gives? Why won't React trigger a re-render when we modified the state? The answer is in how React compares state & handles the virtual DOM, let's take a look under the hood.

In Shakespearean times, one would simply do direct DOM manipulation in Javascript by manipulating the properties of elements:

[Code example of plain Javascript DOM manipulation]

Making DOM changes can slow your application down if you're making lot's of complex updates to the DOM, React solves this by creating a virtual DOM, a light-weight representation of the real DOM. When you update state in React, it creates a new version of the virtual DOM, and batches changes together into a single pass so that only necessary updates to the real DOM are committed.

Check out this example:

function updateUserList(users) {
  const list = document.getElementById('user-list');
  
  // Clear and rebuild EVERYTHING
  list.innerHTML = '';
  
  users.forEach(user => {
    const li = document.createElement('li');
    li.textContent = user.name;
    li.className = user.active ? 'active' : 'inactive';
    list.appendChild(li);  // DOM operation for EACH item
  });
}

// If you have 1000 users and ONE name changes:
// - You clear all 1000 elements
// - You create 1000 new elements
// - You do 1000 DOM appendChild operations
// Total: ~2000+ DOM operations

Here is how React's optimizations save us on performance:

function UserList({ users }) {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id} className={user.active ? 'active' : 'inactive'}>
          {user.name}
        </li>
      ))}
    </ul>
  );
}

// If ONE user's name changes:
// - React diffs the Virtual DOM
// - Identifies only ONE <li> changed
// - Updates ONLY that li's textContent
// Total: 1 DOM operation

When building data-intensive applications, this is a MASSIVE difference.

Virtual DOM Difference Checks

Great, we understand why the virtual DOM is a win for developers working with lot's of data on the front-end, but how does React check for differences to know that state has been updated?

There are multiple ways to compare data stored inside of variables, you can compare the values, or you can compare the references. In Javascript, primatives are compared by their values:

{FINISH CODE EXAMPLE}

let a = 7;
let b = a;

b = 8;

// Javascript will check the actual values of the numbers to see if they are equal, as expected 
const areNumsEqual = (a, b) => a == b;

So far so good, we get into trouble when we get to the same example using objects. Object comparison in Javascript checks the memory address reference for the object, and if it's the same address, Javascript will say the objects are identical (even if the properties are different):

let person = { name: "Michael", occupation: "software engineer" };

person.name = "John";

didChangeHappen() // This will return false, even though we updated the name on John, 

Javascript will see that our 'person' object, still refers to the same memory address even though we updated the name. This is important to know, because in our earlier React example, if you state by mutating the state object directly, instead of the proper way which generates a new object (and thus, a different memory address in memory), then you'll be expecting a React render because state changed and none will arrive.

This is a feature not a bug, object comparisons checking by reference instead of by value is part of the Javascript language specs, it's not something that is specific to React. Knowing this helps you to understand the proper way to update state in React, and is why libraries like Immer have achieved such popularity in the React community, because they provide a concise and intuitive way of updating state object properties (even if they are deeply nested), and ensuring that your created state variables are immutable by creating new state objects with the changes instead of directly modifying state.

References

Linked Citations

— michael
Found this useful? Connect on LinkedIn or hire me for senior AI engineering / fractional CTO work.
NORMAL main ~/writing/2026-05-react-immutable-state.md markdown utf-8 1:1 Top