React.js course ยท Lecture 11 of 27

Managing Object State & Immutability

A masterclass tutorial covering Object State, Updating Object Properties, Spread Operator (...), Nested Object Updates, Immutability Patterns, Common Pitfalls, and Interview Questions.

  • 30 min read
  • Intermediate
  • 8 topics
  • 20 Sept 2026
  • Ravindra Nath Jha

What is Object State in React?

In complex applications, component state is frequently stored as a JavaScript Object containing multiple related fields (user profile forms, settings configurations, address details).

JavaScript
const [user, setUser] = useState({
  name: "Ravindra Nath Jha",
  email: "ravindra@example.com",
  role: "Full Stack Engineer",
  isVerified: true
});

The Immutability Rule for Objects

CRITICAL RULE: In React, never mutate object state directly.

JavaScript
// โŒ WRONG (Mutates existing memory reference - NO RE-RENDER OCCURS!)
user.name = "Amit Kumar";
setUser(user);

// โœ… CORRECT (Creates a BRAND NEW object reference - RE-RENDER TRIGGERS!)
setUser({
  ...user,
  name: "Amit Kumar"
});

Updating Object Properties using the Spread Operator (...)

The ES6 Spread Operator (...) copies all existing key-value pairs from the old object into a new object shell, allowing you to selectively override specific properties:

JavaScript
const handleUpdateEmail = (newEmail) => {
  setUser(prevUser => ({
    ...prevUser,           // Copy all existing properties (name, role, isVerified)
    email: newEmail        // Override ONLY email field
  }));
};

Updating Deeply Nested Objects

When an object contains nested sub-objects, you MUST spread every level of nesting to maintain reference immutability:

JavaScript
const [person, setPerson] = useState({
  name: "Ravindra",
  location: {
    city: "Delhi",
    country: "India"
  }
});

// โœ… CORRECT: Spreading nested location object:
setPerson(prev => ({
  ...prev,
  location: {
    ...prev.location,
    city: "Bengaluru"
  }
}));

Handling Dynamic Input Key Updates (Computed Property Names)

When building forms with multiple input fields, use ES6 Computed Property Names ([e.target.name]) to update any form object property dynamically with a single handler function:

JavaScript
const handleChange = (e) => {
  const { name, value } = e.target;
  setFormData(prev => ({
    ...prev,
    [name]: value // Dynamically updates property matching input name attribute
  }));
};

Common Mistakes with Object State

  1. Forgetting the Spread Operator: Writing setUser({ name: "Ravindra" }) deletes all other properties (email, role) from the state object!
  2. Mutating Nested Objects: Spreading top-level object but mutating nested child object directly.
  3. Omitting Parentheses in Arrow Function Returns: Writing prev => { ...prev } evaluates to an empty block statement instead of returning an object! Wrap objects in parentheses: prev => ({ ...prev }).

Top React Interview Questions on Object State

Q1: Why must we use the spread operator when updating object state?

*Answer*: useState setter replaces the entire state value. Spreading copies existing properties so that un-edited fields are not lost during the state update.

Q2: Why does React fail to re-render when you mutate an object property directly?

*Answer*: React performs a fast shallow reference check (Object.is). Direct mutations keep the same memory address reference, leading React to assume state has not changed.

Q3: How do you update a dynamic object property inside an input handler?

*Answer*: Use ES6 computed property key syntax: setUser(prev => ({ ...prev, [e.target.name]: e.target.value })).

Practical Hands-On Exercise โ€” User Profile Card Editor

Here is a complete interactive user profile editor component demonstrating shallow and nested object updates:

JavaScript
// Complete Masterclass Object State Showcase (src/App.jsx)
import React, { useState } from 'react';

export default function App() {
  const [profile, setProfile] = useState({
    username: "ravindra_uiet",
    email: "ravindra@example.com",
    address: {
      city: "New Delhi",
      zipCode: "110001"
    }
  });

  const handleInputChange = (e) => {
    const { name, value } = e.target;
    setProfile(prev => ({
      ...prev,
      [name]: value
    }));
  };

  const handleCityChange = (newCity) => {
    setProfile(prev => ({
      ...prev,
      address: {
        ...prev.address,
        city: newCity
      }
    }));
  };

  return (
    <div style={{ maxWidth: "550px", margin: "30px auto", padding: "24px", background: "#ffffff", border: "1px solid #cbd5e1", borderRadius: "16px" }}>
      <h2 style={{ color: "#002057" }}>Lecture 11: Object State & Immutability</h2>

      <div style={{ marginBottom: "15px" }}>
        <label>Username: </label>
        <input 
          type="text" 
          name="username"
          value={profile.username} 
          onChange={handleInputChange}
          style={{ width: "100%", padding: "8px", marginTop: "4px", borderRadius: "6px", border: "1px solid #cbd5e1" }}
        />
      </div>

      <div style={{ marginBottom: "15px" }}>
        <label>Email Address: </label>
        <input 
          type="email" 
          name="email"
          value={profile.email} 
          onChange={handleInputChange}
          style={{ width: "100%", padding: "8px", marginTop: "4px", borderRadius: "6px", border: "1px solid #cbd5e1" }}
        />
      </div>

      <div style={{ marginBottom: "20px" }}>
        <label>City (Nested Location): </label>
        <input 
          type="text" 
          value={profile.address.city} 
          onChange={(e) => handleCityChange(e.target.value)}
          style={{ width: "100%", padding: "8px", marginTop: "4px", borderRadius: "6px", border: "1px solid #cbd5e1" }}
        />
      </div>

      {/* Live State JSON Display */}
      <div style={{ padding: "12px", background: "#0f172a", color: "#38bdf8", borderRadius: "8px", fontFamily: "monospace" }}>
        <pre>{JSON.stringify(profile, null, 2)}</pre>
      </div>
    </div>
  );
}