Skip to main content

JavaScript Spread Operator

Introduction

The spread operator (...) is one of the most versatile features introduced in ES6 (ECMAScript 2015). It allows an iterable (like an array or string) to be expanded into individual elements. In React development, the spread operator is extremely valuable for handling state updates, passing props, and working with arrays and objects immutably.

By the end of this lesson, you'll understand how to use the spread operator to:

  • Copy arrays and objects
  • Merge arrays and objects
  • Pass multiple arguments to functions
  • Create immutable updates (essential for React)

Basic Syntax

The spread operator is represented by three consecutive dots (...):

javascript
const array = [1, 2, 3];
console.log(...array); // Output: 1 2 3

Let's dive deeper into how it works in different contexts.

Spreading Arrays

Copying Arrays

One of the most common uses of the spread operator is to create a shallow copy of an array:

javascript
const originalArray = [1, 2, 3];
const copyArray = [...originalArray];

console.log(copyArray); // Output: [1, 2, 3]

// Proof that it's a separate copy
originalArray.push(4);
console.log(originalArray); // Output: [1, 2, 3, 4]
console.log(copyArray); // Output: [1, 2, 3] - unchanged!

Merging Arrays

The spread operator makes combining arrays extremely simple:

javascript
const array1 = [1, 2, 3];
const array2 = [4, 5, 6];

const mergedArray = [...array1, ...array2];
console.log(mergedArray); // Output: [1, 2, 3, 4, 5, 6]

// You can also insert elements in between
const insertedArray = [...array1, 10, 11, ...array2];
console.log(insertedArray); // Output: [1, 2, 3, 10, 11, 4, 5, 6]

Creating Arrays with Additional Elements

You can easily add elements to a new array:

javascript
const numbers = [1, 2, 3];
const moreNumbers = [...numbers, 4, 5];

console.log(moreNumbers); // Output: [1, 2, 3, 4, 5]

// Add elements at the beginning
const evenMoreNumbers = [0, ...numbers];
console.log(evenMoreNumbers); // Output: [0, 1, 2, 3]

Spreading Objects

The spread operator was extended to objects in ES2018, making it even more powerful.

Copying Objects

Similar to arrays, you can create a shallow copy of an object:

javascript
const person = { name: 'John', age: 30 };
const personCopy = { ...person };

console.log(personCopy); // Output: { name: 'John', age: 30 }

// Proof that it's a separate copy
person.age = 31;
console.log(person); // Output: { name: 'John', age: 31 }
console.log(personCopy); // Output: { name: 'John', age: 30 } - unchanged!

Merging Objects

Combining objects is straightforward:

javascript
const personalInfo = { name: 'Sarah', age: 25 };
const careerInfo = { job: 'Developer', experience: '3 years' };

const profile = { ...personalInfo, ...careerInfo };
console.log(profile);
// Output: { name: 'Sarah', age: 25, job: 'Developer', experience: '3 years' }

Overriding Properties

If properties conflict, the last one wins:

javascript
const defaultSettings = { theme: 'light', fontSize: 14, showSidebar: true };
const userSettings = { theme: 'dark' };

const appliedSettings = { ...defaultSettings, ...userSettings };
console.log(appliedSettings);
// Output: { theme: 'dark', fontSize: 14, showSidebar: true }

// Order matters
const incorrectMerge = { ...userSettings, ...defaultSettings };
console.log(incorrectMerge);
// Output: { theme: 'light', fontSize: 14, showSidebar: true } - user's theme preference is lost!

Spreading Function Arguments

The spread operator can expand an array into individual arguments for a function call.

Passing Multiple Arguments

javascript
function sum(a, b, c) {
return a + b + c;
}

const numbers = [1, 2, 3];
const result = sum(...numbers);

console.log(result); // Output: 6

Combining with Regular Arguments

javascript
function introduce(greeting, name, ...interests) {
return `${greeting}, I'm ${name}. I like ${interests.join(", ")}.`;
}

const person = ['Alice'];
const hobbies = ['coding', 'reading', 'hiking'];

const introduction = introduce('Hi', ...person, ...hobbies);
console.log(introduction);
// Output: Hi, I'm Alice. I like coding, reading, hiking.

Practical Examples for React

The spread operator is essential for React development, especially for immutability patterns. Let's look at some common use cases:

Updating State Immutably

When updating state in React, you should never modify the original state directly:

javascript
// Updating a simple state object
const [user, setUser] = useState({ name: 'Alex', email: '[email protected]' });

// Wrong way (modifying state directly)
// user.email = '[email protected]';
// setUser(user);

// Correct way (using spread operator)
setUser({
...user,
email: '[email protected]'
});

Adding Items to an Array in State

javascript
const [todos, setTodos] = useState(['Learn React', 'Practice JavaScript']);

// Adding a new todo
const addTodo = (newTodo) => {
setTodos([...todos, newTodo]);
};

// Later in your code
addTodo('Build a project');
// Result: ['Learn React', 'Practice JavaScript', 'Build a project']

Updating Nested Objects

For nested objects, you need to apply the spread operator at each level that needs updating:

javascript
const [profile, setProfile] = useState({
name: 'Jordan',
preferences: {
darkMode: false,
notifications: true
}
});

// Toggle dark mode
setProfile({
...profile,
preferences: {
...profile.preferences,
darkMode: !profile.preferences.darkMode
}
});

Copying Props in Components

The spread operator makes it easy to forward props to child components:

jsx
function Button(props) {
// Extract specific props
const { color, ...restProps } = props;

// Use the extracted props
const buttonStyle = { backgroundColor: color };

// Pass the remaining props to the button element
return <button style={buttonStyle} {...restProps} />;
}

// Usage:
<Button color="blue" onClick={handleClick} disabled={isLoading}>
Submit
</Button>

Advanced Patterns

Conditionally Adding Properties to Objects

You can conditionally add properties using the spread operator with a conditional object:

javascript
const createUser = (username, isPremium) => {
return {
id: generateId(),
username,
createdAt: new Date(),
...(isPremium && {
premiumFeatures: true,
expiresAt: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000) // 30 days from now
})
};
};

const regularUser = createUser('user123', false);
console.log(regularUser);
// Output: { id: "...", username: "user123", createdAt: "..." }

const premiumUser = createUser('premium456', true);
console.log(premiumUser);
// Output: { id: "...", username: "premium456", createdAt: "...", premiumFeatures: true, expiresAt: "..." }

Spread with Rest Parameters

You can combine spread with rest parameters for powerful function patterns:

javascript
function filterAndModify(modifier, ...items) {
return items.map(item => modifier(item))
.filter(item => item !== undefined);
}

const numbers = [1, 2, 3, 4, 5];

const doubleOddNumbers = filterAndModify(
num => num % 2 === 1 ? num * 2 : undefined,
...numbers
);

console.log(doubleOddNumbers); // Output: [2, 6, 10]

Common Pitfalls

Shallow vs Deep Copy

The spread operator only creates a shallow copy. Nested objects are still referenced, not duplicated:

javascript
const user = {
name: 'Alex',
settings: {
darkMode: true,
fontSize: 14
}
};

const userCopy = { ...user };

// Modifying a nested object affects both original and copy
userCopy.settings.darkMode = false;

console.log(user.settings.darkMode); // Output: false
console.log(userCopy.settings.darkMode); // Output: false

For deep copies, you might need additional approaches:

javascript
// Using JSON (works for simple objects without functions, Date objects, etc.)
const deepCopy = JSON.parse(JSON.stringify(user));

// Or better, using a library like lodash's cloneDeep
// const deepCopy = _.cloneDeep(user);

Order Matters with Object Spread

When spreading objects, the order determines which properties "win" in case of conflicts:

javascript
const defaultStyles = { color: 'black', fontSize: 16 };
const userStyles = { color: 'blue' };

// User styles override defaults
const styles1 = { ...defaultStyles, ...userStyles };
console.log(styles1.color); // Output: 'blue'

// Default styles override user preferences (usually not what you want!)
const styles2 = { ...userStyles, ...defaultStyles };
console.log(styles2.color); // Output: 'black'

Summary

The spread operator (...) is a powerful JavaScript feature that simplifies working with arrays and objects. It allows you to:

  • Create copies of arrays and objects
  • Merge multiple arrays or objects together
  • Pass array elements as separate arguments to functions
  • Update state immutably in React applications
  • Extract or collect properties from objects

Mastering the spread operator is essential for modern JavaScript development, especially when working with React where immutability is a core concept.

Practice Exercises

  1. Create a function that takes an array of numbers and returns a new array with each number doubled, using the spread operator.
  2. Write a React-style function that updates a user object, changing only the email property while keeping all other properties the same.
  3. Create a function that merges two arrays but removes any duplicate values.
  4. Write a utility function that takes an object and returns a new object with specific properties removed.

Additional Resources



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)