structuredClone() and Immutable Update Patterns in WordPress JavaScript

structuredClone() is the native browser and Node.js API for deep-cloning objects — it handles circular references, typed arrays, Maps, Sets, and Dates correctly, replacing the fragile JSON.parse(JSON.stringify()) hack used in WordPress JavaScript code.

Problem: JavaScript code that spreads or copies objects before mutation — state updates in a WordPress admin SPA, immutable reducer patterns — uses JSON.parse(JSON.stringify(obj)) to deep clone, which fails on Date, Map, RegExp, and circular references.

Solution: Use the native structuredClone() function — it handles all serialisable types correctly, including Date, Map, Set, ArrayBuffer, and circular references. It is available in all modern browsers and Node.js 17+, requires no library, and is significantly faster than the JSON round-trip approach.

The examples below compare cloning strategies, show how structuredClone() handles complex types, and demonstrate immutable update patterns for WordPress block editor state management.

// ── Old fragile deep clone ──
const clone1 = JSON.parse( JSON.stringify( obj ) );
// Problems: loses Date objects (converts to string), undefined values, functions,
//           Map/Set (converts to {}), and throws on circular references.

// ── structuredClone() — native, correct ──
const original = {
    title: 'My Post',
    date: new Date( '2024-08-02' ),         // Date preserved
    tags: new Set( ['wordpress', 'php'] ),   // Set preserved
    meta: new Map( [['key', 'value']] ),     // Map preserved
    nested: { count: 42 },
};

const clone = structuredClone( original );

clone.nested.count = 99;
console.log( original.nested.count ); // 42 — original unchanged
console.log( clone.date instanceof Date ); // true
console.log( clone.tags instanceof Set  ); // true

// What structuredClone CANNOT clone (throws DataCloneError):
// - Functions
// - DOM nodes
// - class instances (prototype is lost — becomes plain object)
// - WeakMap / WeakSet

Immutable update patterns for Gutenberg block editor state:

// In a Gutenberg block's edit() function, attributes must not be mutated.
// Use structuredClone to create a deep copy before modifying.

function updateNestedAttribute( attributes, setAttributes ) {
    // BAD — mutates the existing attributes object:
    // attributes.settings.display = 'grid';

    // GOOD — deep clone first, then modify:
    const newAttributes = structuredClone( attributes );
    newAttributes.settings.display = 'grid';
    setAttributes( newAttributes );
}

// Immutable array update patterns (no mutation):
const items = [ { id: 1, done: false }, { id: 2, done: false } ];

// Toggle item 1 — spread for shallow, structuredClone for deep
const updated = items.map( item =>
    item.id === 1 ? { ...item, done: true } : item
);

// Remove an item immutably
const filtered = items.filter( item => item.id !== 1 );

// Add an item immutably
const appended = [ ...items, { id: 3, done: false } ];

// For deeply nested structures in a Redux-style store (wp.data):
const updateDeepState = ( state, blockId, newValue ) =>
    structuredClone( {
        ...state,
        blocks: state.blocks.map( b =>
            b.clientId === blockId ? { ...b, attributes: { ...b.attributes, value: newValue } } : b
        ),
    } );

NOTE: structuredClone() is available in all modern browsers and Node.js 17+. For class instances where the prototype matters, use a custom clone() method or a library like lodash.cloneDeep — but for plain data objects (the vast majority of WordPress JS state), structuredClone() is the correct choice.

Leave Comment

Your email address will not be published. Required fields are marked *