DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

Beyond the Spread: **`structuredClone`** vs `{ ...obj }` — Deep-Copy Tactics Every React + TypeScript Engineer Should Master

Beyond the Spread: ** raw `structuredClone` endraw ** vs  raw `{ ...obj }` endraw  — Deep-Copy Tactics Every React + TypeScript Engineer Should Master

Beyond the Spread: structuredClone vs { ...obj } — Deep-Copy Tactics Every React + TypeScript Engineer Should Master

Because sometimes a shallow copy just won’t cut it.


Why This Matters

In React we preach immutability: instead of mutating objects in place, you copy them, update the copy, and let React diff the changes. That’s easy for primitives and flat objects, but nested data structures can trip you up:

const updated = { ...user, address: { ...user.address, city: 'Tokyo' } };
Enter fullscreen mode Exit fullscreen mode

Looks innocent… but becomes spaghetti when objects are big, or arrays are nested three layers deep. Enter structuredClone (a browser‑native deep copier) and a fresh perspective on when the spread operator is still the best tool in your belt.


1 Setting the Stage

interface Person {
  firstName: string;
  lastName:  string;
  age:       number;
  address:   Address;
}

interface Address {
  postalCode: string;
  city:       string;
}

const ironman: Person = {
  firstName: 'Tony',
  lastName:  'Stark',
  age:       45,
  address:   { postalCode: 'ABC-123', city: 'New York' },
};
Enter fullscreen mode Exit fullscreen mode

We’ll clone ironman with both approaches:

const deepClone   = structuredClone(ironman); // ✨
const shallowCopy = { ...ironman };           // 🧩
Enter fullscreen mode Exit fullscreen mode

2 Spread Operator { ...obj } – What Actually Gets Copied?

Fields on the first level are copied by value. Nested objects or arrays are copied by reference (shallow copy).

shallowCopy.address.city = 'Malibu';
console.log(ironman.address.city); // ➜ 'Malibu'  😱
Enter fullscreen mode Exit fullscreen mode

Good enough when:

  • Your state is flat.
  • You immediately overwrite nested objects ({ ...state, nested: { ...state.nested, x: 1 }}).
  • Performance matters; spread is faster and zero‑allocation for nested refs.

3 structuredClone — Deep Copy in One Breath

How it works

  • Recurses through objects, arrays, Maps, Sets, Dates, RegExps, even ArrayBuffers.
  • Skips functions, DOM nodes, and unserializable classes (will throw).
  • Keeps prototype chain? No. You get plain objects — perfect for serializable data but not class instances.
deepClone.address.city = 'Malibu';
console.log(ironman.address.city); // ➜ 'New York' ✅
Enter fullscreen mode Exit fullscreen mode

Browser Support (July 2025)

Engine Version
Chrome 98+
Edge 98+
Firefox 94+
Safari 15.4+
Node 17+

4 Performance Showdown

Scenario { ...obj } structuredClone
Flat 1‑level object ⚡ fastest (single allocation) ~2‑5× slower
10 kB nested tree O(n) shallow (leaves shared) copies entire graph — 2‑10× slower
Memory overhead Minimal Proportional to data size

👉 Rule of thumb:

Use structuredClone when you need correctness > micro‑speed, like editing drafts, implementing undo stacks, or copying API payloads before mutation.


5 React Patterns

A. Local component state

const [draft, setDraft] = useState<Person>(ironman);

function handleCityChange(city: string) {
  setDraft(prev => ({
    ...prev,
    address: { ...prev.address, city }, // OK: nested overwrite
  }));
}
Enter fullscreen mode Exit fullscreen mode

B. Heavy forms / editors

Using Immer or zustand with structuredClone to snapshot pre‑edit state:

import { useRef } from 'react';

const history = useRef<Person[]>([]);

function startEdit() {
  history.current.push(structuredClone(user));
}
Enter fullscreen mode Exit fullscreen mode

C. Redux Toolkit (RTK Query)

RTK uses Immer under the hood, but if you need a totally separate draft (e.g., optimistic update rollback), deep‑clone the cached entity:

const original = structuredClone(post);
Enter fullscreen mode Exit fullscreen mode

6 Edge Cases & Gotchas

⚠️ Pitfall Fix
Copying class instances loses methods Write a custom .clone() or use shallow pattern + manual constructor
Circular references? Supported out of the box by structuredClone
Functions / DOM nodes throw Strip them or keep a ref map
Large data (MBs) cloned every keystroke Debounce, or clone only diff portions

7 Cheat‑Sheet

Need Clone Strategy
Flat object, small { ...obj }
Add/remove key without touching nested props { ...obj, newProp }
Deep nested update but only one branch Spread that branch: { ...o, nested: { ...o.nested, x } }
Snapshot entire state for Undo / optimistic UI structuredClone(obj)
Binary data, Map, Set structuredClone(obj)
Old browsers Ponyfill or JSON.parse(JSON.stringify(obj)) (lossy)

Final Thoughts

Think of cloning as a surgical tool:

  • Spread is a scalpel — precise, minimal overhead, but fails on deep wounds.
  • structuredClone is the MRI copy machine — heavyweight but gives you a perfect replica to operate on risk‑free.

Master both and you’ll write React components that are predictable under the DevTools microscope and performant enough for production.

Happy cloning — and remember: with great copies comes great responsibility! 🕸️


✍️ Written by: Cristian Sifuentes – Full-stack dev crafting scalable apps with [NET - Azure], [Angular - React], Git, SQL & extensions. Clean code, dark themes, atomic commits

Top comments (1)

Collapse
 
sharmaricky profile image
VS

structuredClone has a very narrow scope—most objects in real-world applications include methods or functions, which it doesn't preserve. Cloning an object while stripping away its methods is often impractical. Nonetheless, this was a well-written article—thanks for sharing!