React ES6 Spread Operator

The spread operator (...) expands arrays and objects into individual elements, and it's the standard tool for copying and combining props and state immutably in React.

Spreading arrays

The spread operator, written as three dots, expands an iterable — most commonly an array — into its individual elements wherever it's used: inside another array literal, as function arguments, or when copying an array shallowly.

Array spread basics

export default function App() {
  const fruits = ['apple', 'banana'];
  const moreFruits = [...fruits, 'cherry', 'date'];
  console.log(moreFruits); // ['apple', 'banana', 'cherry', 'date']

  // Shallow copy
  const copy = [...fruits];
  copy.push('elderberry');
  console.log(fruits);  // unchanged: ['apple', 'banana']
  console.log(copy);    // ['apple', 'banana', 'elderberry']

  // Combining two arrays
  const a = [1, 2];
  const b = [3, 4];
  const combined = [...a, ...b]; // [1, 2, 3, 4]

  return (
    <div>
      <p>moreFruits: {moreFruits.join(', ')}</p>
      <p>fruits (unchanged): {fruits.join(', ')}</p>
      <p>copy: {copy.join(', ')}</p>
      <p>combined: {combined.join(', ')}</p>
      <p>Open the console to see the logged values.</p>
    </div>
  );
}

Spreading objects

Object spread (added alongside rest/spread properties, standardized in ES2018 and universally supported in React tooling) copies enumerable own properties into a new object literal. This is the foundation of immutable state updates in React, since useState setters and reducers should never mutate the existing state object directly.

Object spread for immutable updates

export default function App() {
  const user = { id: 1, name: 'Lee', age: 29 };

  // Create an updated copy without mutating the original
  const olderUser = { ...user, age: 30 };
  console.log(user);      // { id: 1, name: 'Lee', age: 29 } — untouched
  console.log(olderUser); // { id: 1, name: 'Lee', age: 30 }

  // Later keys override earlier ones with the same name
  const withDefaults = { theme: 'light', size: 'medium' };
  const finalConfig = { ...withDefaults, theme: 'dark' };
  console.log(finalConfig); // { theme: 'dark', size: 'medium' }

  return (
    <div>
      <p>user: {JSON.stringify(user)}</p>
      <p>olderUser: {JSON.stringify(olderUser)}</p>
      <p>finalConfig: {JSON.stringify(finalConfig)}</p>
      <p>Open the console to see the logged values.</p>
    </div>
  );
}

Spread in React state updates

React relies on comparing object references to know when to re-render, so mutating state directly (e.g. `state.items.push(x)`) can silently fail to trigger an update. Spreading into a new array or object guarantees a fresh reference.

Updating array and object state without mutation

import { useState } from 'react';

function TodoApp() {
  const [todos, setTodos] = useState([{ id: 1, text: 'Learn spread', done: false }]);

  function addTodo(text) {
    setTodos((prev) => [...prev, { id: Date.now(), text, done: false }]);
  }

  function toggleTodo(id) {
    setTodos((prev) =>
      prev.map((todo) =>
        todo.id === id ? { ...todo, done: !todo.done } : todo
      )
    );
  }

  return (
    <ul>
      {todos.map((t) => (
        <li key={t.id} onClick={() => toggleTodo(t.id)}>
          {t.done ? '✅' : '⬜'} {t.text}
        </li>
      ))}
    </ul>
  );
}

export default TodoApp;

Spreading props onto JSX

Spread also works directly inside JSX to forward a whole object of props onto an element, which is common in wrapper components and design systems.

Spreading props onto an element

function IconButton({ icon, ...buttonProps }) {
  return (
    <button {...buttonProps}>
      {icon} {buttonProps.children}
    </button>
  );
}

export default function App() {
  // `type`, `onClick`, and `disabled` all flow through automatically
  return (
    <IconButton icon="🔍" type="button" onClick={() => console.log('search')} disabled={false}>
      Search
    </IconButton>
  );
}
  • Spread copies are shallow — nested objects/arrays are still shared by reference after a spread.
  • Property order matters when spreading objects: later keys always win over earlier ones.
  • Spread in a function call (fn(...args)) passes array elements as separate arguments.
  • Spread is the immutable counterpart to Object.assign({}, obj, updates) and Array.prototype.concat.
Use caseSyntaxResult
Copy an array[...arr]New array, same elements
Merge two objects{...a, ...b}New object, b's keys win on conflict
Add an item immutably[...arr, item]New array with item appended
Update one field in state{...state, field: newValue}New object, only field changed
Note: Spread and rest use identical (...) syntax but opposite directions: spread expands a collection into individual items (used on the right-hand side, e.g. in a literal or function call), while rest gathers individual items into a collection (used on the left-hand side, e.g. in destructuring or parameters).
Note: Because object and array spread only copy one level deep, `{ ...state, address: state.address }` still points at the same nested address object. If you mutate `newState.address.city = 'X'` afterward, you are mutating the original state's address too — you must spread the nested level as well: `{ ...state, address: { ...state.address, city: 'X' } }`.

Exercise: React ES6 Spread Operator

What does `const copy = [...originalArray]` create?