Objects and Arrays: Why Can't You Just " Change " ?

JavaScript has a huge trap: objects and arrays are passed by reference. If you don't understand how this works, your React site will " glitch " and not update.

1. The Reference Trap

Imagine your state is a house address. If you go into the house and repaint the walls (changed an object property), the house address remains the same. React looks at the address: " Hmm, same address, means the house hasn't changed. Won't redraw! "

const [user, setUser] = useState({ name: 'Ivan' });

// ❌ ERROR:
user.name = 'Dmitry'; // We changed the data, but the reference is the same
setUser(user);        // React will ignore this call

2. Golden Rule: Always Create a " Clone "

For React to see changes, you must give it a new reference (new address). We don't change the old object, we create a new one based on the old. For this, we use the magic of three dots — Spread Operator (...).

// ✅ CORRECT:
setUser({ 
  ...user,         // Copy everything old
  name: 'Dmitry'   // But write the new name
});

Now React sees: " Oh, this is a new object! Time to update the screen. "

3. Arrays: No push and splice

Methods push, pop, splice change the old array (mutate it). In React they're forbidden. Instead, use methods that return a new array:

  • Adding: setItems([...items, newItem])
  • Removing: setItems(items.filter(i => i.id !== id))
  • Changing: setItems(items.map(i => i.id === id ? { ...i, val } : i))

4. Update via Function (Updater pattern)

If your new state depends on the old (counter, toggle), always use a function inside set:

setCount(prev => prev + 1);

Why is this needed? If a user presses a button 10 times per second, regular setCount(count + 1) might work incorrectly due to " snapshots " . Functional update guarantees you always take the freshest value from React's queue.

Think of immutability as insurance. If you never change old data, you can always go back and check how the site looked a second ago.