logoESLint React
Rules

immutability

Validates against mutating props, state, and other values that are immutable.

This rule is currently in rc and only available in v3.0.0 rc releases.

This rule is experimental and may change in the future or be removed. It is not recommended for use in production code at this time.

Full Name in eslint-plugin-react-x@rc

react-x/immutability

Full Name in @eslint-react/eslint-plugin@rc

@eslint-react/immutability

Features

🧪

Presets

Rule Details

A component's props and state are immutable snapshots. Never mutate them directly. Instead, pass new props down, and use the setter function from useState with a freshly created value.

Mutations are invisible to React — because the object or array reference hasn't changed, React has no way to know that something changed. As a result, the UI won't re-render, and your app will show stale data.

Common Violations

Invalid

// ❌ Array push mutation
function Component() {
  const [items, setItems] = useState([1, 2, 3]);
  const addItem = () => {
    items.push(4); // Mutating!
    setItems(items); // Same reference, no re-render
  };
}
// ❌ Object property assignment
function Component() {
  const [user, setUser] = useState({ name: "Alice" });
  const updateName = () => {
    user.name = "Bob"; // Mutating!
    setUser(user); // Same reference
  };
}
// ❌ Sort without spreading
function Component() {
  const [items, setItems] = useState([3, 1, 2]);
  const sortItems = () => {
    setItems(items.sort()); // sort mutates!
  };
}

Valid

// ✅ Create new array
function Component() {
  const [items, setItems] = useState([1, 2, 3]);
  const addItem = () => {
    setItems([...items, 4]); // New array
  };
}
// ✅ Create new object
function Component() {
  const [user, setUser] = useState({ name: "Alice" });
  const updateName = () => {
    setUser({ ...user, name: "Bob" }); // New object
  };
}
// ✅ Spread before sorting
function Component() {
  const [items, setItems] = useState([3, 1, 2]);
  const sortItems = () => {
    setItems([...items].sort()); // New array, then sort
  };
}

Troubleshooting

I need to add items to an array

Mutating arrays with methods like push() won't trigger re-renders:

// ❌ Wrong: Mutating the array
function TodoList() {
  const [todos, setTodos] = useState([]);
  const addTodo = (id, text) => {
    todos.push({ id, text });
    setTodos(todos); // Same array reference!
  };
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

Create a new array instead:

// ✅ Better: Create a new array
function TodoList() {
  const [todos, setTodos] = useState([]);
  const addTodo = (id, text) => {
    setTodos([...todos, { id, text }]);
    // Or: setTodos((todos) => [...todos, { id: Date.now(), text }])
  };
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}

I need to update nested objects

Mutating nested properties doesn't trigger re-renders:

// ❌ Wrong: Mutating nested object
function UserProfile() {
  const [user, setUser] = useState({
    name: "Alice",
    settings: {
      theme: "light",
      notifications: true,
    },
  });
  const toggleTheme = () => {
    user.settings.theme = "dark"; // Mutation!
    setUser(user); // Same object reference
  };
}

Spread at each level that needs updating:

// ✅ Better: Create new objects at each level
function UserProfile() {
  const [user, setUser] = useState({
    name: "Alice",
    settings: {
      theme: "light",
      notifications: true,
    },
  });
  const toggleTheme = () => {
    setUser({
      ...user,
      settings: {
        ...user.settings,
        theme: "dark",
      },
    });
  };
}

I need to sort or reverse a list

Array.prototype.sort() and Array.prototype.reverse() sort and reverse the array in place, mutating the original:

// ❌ Wrong: sort/reverse mutates the state array
function SortedList() {
  const [items, setItems] = useState([3, 1, 2]);
  const sort = () => {
    setItems(items.sort()); // items is mutated!
  };
  return <button onClick={sort}>Sort</button>;
}

Spread into a new array first:

// ✅ Correct: spread into a new array before sorting
function SortedList() {
  const [items, setItems] = useState([3, 1, 2]);
  const sort = () => {
    setItems([...items].sort());
  };
  return <button onClick={sort}>Sort</button>;
}

Common Violations

Invalid

import { useState } from "react";

function Component() {
  const [items, setItems] = useState([1, 2, 3]);
  const addItem = () => {
    items.push(4);
    // ^^^ Do not call 'push()' on 'items'. Props and state are immutable — create a new array instead.
    setItems(items);
  };
  return <div>{items.length}</div>;
}
import { useState } from "react";

function Component() {
  const [user, setUser] = useState({ name: "Alice" });
  const updateName = () => {
    user.name = "Bob";
    // ^^^ Do not mutate 'user' directly. Props and state are immutable — create a new object instead.
    setUser(user);
  };
  return <div>{user.name}</div>;
}
function Component(props) {
  props.name = "Bob";
  // ^^^ Do not mutate 'props' directly. Props and state are immutable — create a new object instead.
  return <div>{props.name}</div>;
}

Valid

import { useState } from "react";

function Component() {
  const [items, setItems] = useState([1, 2, 3]);
  const addItem = () => {
    setItems([...items, 4]); // ✅ New array
  };
  return <div>{items.length}</div>;
}
import { useState } from "react";

function Component() {
  const [user, setUser] = useState({ name: "Alice" });
  const updateName = () => {
    setUser({ ...user, name: "Bob" }); // ✅ New object
  };
  return <div>{user.name}</div>;
}
import { useState } from "react";

function Component() {
  const [items, setItems] = useState([3, 1, 2]);
  const sortItems = () => {
    setItems([...items].sort()); // ✅ Spread first, then sort
  };
  return <div>{items.length}</div>;
}

Resources

Further Reading


See Also

  • react-x/no-direct-mutation-state
    Disallows direct mutation of this.state.
  • react-x/purity
    Validates that components and hooks are pure by checking that they do not call known-impure functions during render.
  • react-x/use-state
    Enforces correct usage of useState, including destructuring, symmetric naming of the value and setter, and wrapping expensive initializers in a lazy initializer function.

On this page