shallowClone
Creates a shallow copy of the given value. Performs shallow cloning of arrays and plain objects by creating new instances with copied references to their elements/properties. Primitive values, functions, and special objects (Date, Map, etc.) are returned as-is without copying.
Signature
const shallowClone: <Type>(value: Type) => Type
Parameters
| Name | Type | Description |
|---|---|---|
value | - | The value to create a shallow copy of |
Returns
A shallow copy of arrays and plain objects, or the original value for other types
Examples
Basic shallow cloning of objects
import { shallowClone } from '@winglet/common-utils';
// Simple object cloning
const original = {
id: 1,
name: 'John',
email: 'john@example.com'
};
const cloned = shallowClone(original);
console.log(cloned); // { id: 1, name: 'John', email: 'john@example.com' }
console.log(cloned !== original); // true (different reference)
console.log(cloned.name === original.name); // true (same primitive value)
// Nested objects share references (shallow copy)
const withNested = {
id: 1,
profile: { name: 'John' }
};
const clonedNested = shallowClone(withNested);
console.log(clonedNested !== withNested); // true (new object)
console.log(clonedNested.profile === withNested.profile); // true (same nested reference)
// Modifying nested objects affects both
clonedNested.profile.name = 'Jane';
console.log(withNested.profile.name); // 'Jane' (shared reference)
Array shallow cloning
// Simple array cloning
const numbers = [1, 2, 3, 4, 5];
const clonedNumbers = shallowClone(numbers);
console.log(clonedNumbers); // [1, 2, 3, 4, 5]
console.log(clonedNumbers !== numbers); // true (different array)
// Array modifications are independent
clonedNumbers.push(6);
console.log(numbers); // [1, 2, 3, 4, 5] (unchanged)
console.log(clonedNumbers); // [1, 2, 3, 4, 5, 6]
// Array with object elements (shallow copy)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
const clonedUsers = shallowClone(users);
console.log(clonedUsers !== users); // true (new array)
console.log(clonedUsers[0] === users[0]); // true (same object reference)
// Modifying elements affects both arrays
clonedUsers[0].name = 'Alicia';
console.log(users[0].name); // 'Alicia' (shared reference)
Primitive values and special types
// Primitives are returned as-is
console.log(shallowClone(42)); // 42
console.log(shallowClone('hello')); // 'hello'
console.log(shallowClone(true)); // true
console.log(shallowClone(null)); // null
console.log(shallowClone(undefined)); // undefined
console.log(shallowClone(Symbol('test'))); // Symbol(test)
console.log(shallowClone(BigInt(123))); // 123n
// Functions are returned as-is
const myFunc = () => 'test';
console.log(shallowClone(myFunc) === myFunc); // true
// Special objects are returned as-is (not cloned)
const date = new Date();
console.log(shallowClone(date) === date); // true (same reference)
const map = new Map([['key', 'value']]);
console.log(shallowClone(map) === map); // true (same reference)
const regex = /pattern/gi;
console.log(shallowClone(regex) === regex); // true (same reference)
State management and immutability
// Redux-style state update
interface AppState {
user: {
id: number;
name: string;
};
settings: {
theme: string;
notifications: boolean;
};
}
const currentState: AppState = {
user: { id: 1, name: 'John' },
settings: { theme: 'dark', notifications: true }
};
// Create new state with updated user
const newState = {
...shallowClone(currentState),
user: { ...currentState.user, name: 'Jane' }
};
console.log(currentState !== newState); // true
console.log(currentState.settings === newState.settings); // true (shared)
Form data handling
// Clone form data before modification
const formData = {
personalInfo: {
firstName: 'John',
lastName: 'Doe'
},
contactInfo: {
email: 'john@example.com',
phone: '+1234567890'
}
};
// Clone for editing without affecting original
const editableData = shallowClone(formData);
editableData.personalInfo = {
...editableData.personalInfo,
firstName: 'Jane'
};
console.log(formData.personalInfo.firstName); // 'John' (unchanged)
console.log(editableData.personalInfo.firstName); // 'Jane'
Component props handling
// React component props shallow cloning
interface ButtonProps {
label: string;
onClick: () => void;
style?: React.CSSProperties;
disabled?: boolean;
}
const defaultProps: ButtonProps = {
label: 'Click me',
onClick: () => console.log('clicked'),
disabled: false
};
// Clone props for customization
const primaryButton = shallowClone(defaultProps);
primaryButton.label = 'Primary Action';
const dangerButton = shallowClone(defaultProps);
dangerButton.label = 'Delete';
dangerButton.style = { color: 'red' };
console.log(defaultProps.label); // 'Click me' (unchanged)
API response processing
// Clone API response for safe mutation
const apiResponse = {
data: [
{ id: 1, name: 'Item 1', metadata: { views: 100 } },
{ id: 2, name: 'Item 2', metadata: { views: 200 } }
],
pagination: {
page: 1,
total: 50
}
};
// Clone for local manipulation
const processedResponse = shallowClone(apiResponse);
processedResponse.data = processedResponse.data.map(item => ({
...item,
name: item.name.toUpperCase()
}));
console.log(apiResponse.data[0].name); // 'Item 1' (unchanged)
console.log(processedResponse.data[0].name); // 'ITEM 1'
console.log(apiResponse.pagination === processedResponse.pagination); // true
Playground
import { shallowClone } from '@winglet/common-utils'; // Simple object cloning const original = { id: 1, name: 'John', email: 'john@example.com' }; const cloned = shallowClone(original); console.log(cloned); // { id: 1, name: 'John', email: 'john@example.com' } console.log(cloned !== original); // true (different reference) console.log(cloned.name === original.name); // true (same primitive value) // Nested objects share references (shallow copy) const withNested = { id: 1, profile: { name: 'John' } }; const clonedNested = shallowClone(withNested); console.log(clonedNested !== withNested); // true (new object) console.log(clonedNested.profile === withNested.profile); // true (same nested reference) // Modifying nested objects affects both clonedNested.profile.name = 'Jane'; console.log(withNested.profile.name); // 'Jane' (shared reference)
Notes
Cloning Strategy:
- Arrays: Creates new array with spread operator
[...value] - Plain Objects: Creates new object with spread operator
{...value} - Primitives: Returns the value unchanged
- Functions: Returns the function reference unchanged
- Special Objects: Returns the object reference unchanged (Date, Map, Set, RegExp, etc.)
Performance Characteristics:
- Time Complexity: O(n) for arrays/objects where n is number of direct properties
- Space Complexity: O(n) for new array/object creation
- Optimization: Uses spread operator for efficient shallow copying
- Fast Path: Primitives and special objects have O(1) performance
Shallow vs Deep Copy:
- Shallow copy creates new top-level structure only
- Nested objects/arrays maintain same references
- Modifications to nested structures affect both copies
- For deep cloning nested structures, use
clonefrom@winglet/common-utils - Suitable for immutable update patterns with selective deep copying
Type Safety:
- Preserves TypeScript type information
- Generic type parameter maintains input/output type consistency
- Type assertions used for array cloning compatibility
Use Cases:
- Creating defensive copies for mutation safety
- Implementing immutable update patterns
- Preparing objects for modification
- State management in frameworks (Redux, Zustand)
- Component prop handling
- API response processing
Comparison with Alternatives:
Object.assign({}, obj): Similar but more verbose{...obj}: Direct spread - same behavior for objects[...arr]: Direct spread - same behavior for arraysArray.slice(): Alternative for arraysclone()from@winglet/common-utils: Deep clone (different purpose)- Lodash
_.clone(): Similar but handles more special cases
Limitations:
- Does not clone nested structures (shallow only)
- Special objects (Date, Map, etc.) are not cloned
- Class instances are returned as references
- Does not handle circular references (not applicable for shallow copy)
- Only processes enumerable own properties
- Does not preserve property descriptors
When to Use:
- Need to create independent top-level copy
- Implementing immutable update patterns
- Preparing object for safe mutation
- Performance-critical scenarios where deep cloning is unnecessary
When Not to Use:
- Need to clone nested structures independently (use
clonefrom@winglet/common-utils) - Need to clone special objects like Date, Map (use specific cloning)
- Working with class instances requiring deep cloning
- Need to preserve all object metadata and descriptors