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' } };
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' },
};
We’ll clone ironman
with both approaches:
const deepClone = structuredClone(ironman); // ✨
const shallowCopy = { ...ironman }; // 🧩
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' 😱
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
ArrayBuffer
s. - 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' ✅
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
}));
}
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));
}
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);
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)
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!