cloneDeep strips Temporal.* instances to {} (loses prototype, internal slots, instanceof)
cloneDeep does not preserve Temporal.* instances. The clone loses its prototype chain, internal slots, and instanceof identity — the result is a plain {} with no Temporal methods. isEqual(original, clone) then returns false, which breaks downstream patterns that depend on "clone then compare" (dirty-state checks, undo/redo snapshots, form models, etc.).
Reproduction
import { cloneDeep, isEqual } from 'lodash';
import 'temporal-polyfill/global';
const date = Temporal.PlainDate.from('2026-05-20');
const clone = cloneDeep(date);
console.log(clone); // {}
console.log(clone instanceof Temporal.PlainDate); // false
console.log(isEqual(date, clone)); // false
Affects every Temporal value type: PlainDate, PlainTime, PlainDateTime, PlainYearMonth, PlainMonthDay, Instant, ZonedDateTime, Duration.
Environment
- lodash 4.18.1
- temporal-polyfill 0.3.2 (same shape will hit native
Temporalonce browsers ship it — the data lives in internal slots, not enumerable own properties) - Node 24 (also reproduces in jsdom / happy-dom / browser)
Why this matters
Temporal types are immutable, so the correct treatment is to return the same reference (no copy needed). lodash currently treats them as plain objects and walks own properties — which on a Temporal instance is the empty set, so the prototype, internal slots, and instanceof identity are all lost.
Workaround
cloneDeepWith with a customizer that short-circuits Temporal instances:
import { cloneDeepWith } from 'lodash';
const isTemporal = (v) =>
v != null && typeof v === 'object' && (
v instanceof Temporal.PlainDate ||
v instanceof Temporal.PlainTime ||
v instanceof Temporal.PlainDateTime ||
v instanceof Temporal.PlainYearMonth ||
v instanceof Temporal.PlainMonthDay ||
v instanceof Temporal.Instant ||
v instanceof Temporal.ZonedDateTime ||
v instanceof Temporal.Duration
);
export const cloneDeep = (value) =>
cloneDeepWith(value, (v) => (isTemporal(v) ? v : undefined));
This works but means every consumer of Temporal-bearing state has to swap their import. A built-in fix would let cloneDeep Just Work for the upcoming Stage 3 Temporal proposal.
Suggested fix
Detect Temporal instances in the clone walker and return them by reference (they're immutable). A symmetric fix in isEqual would also help — isEqual(PlainDate.from('2026-05-20'), PlainDate.from('2026-05-20')) currently returns false because the Temporal Object.prototype.toString tag isn't in equalByTag's known list.
Source: lodash/lodash