Try @eslint-react/kit@beta
logoESLint React

immutability

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

This is an evaluation implementation and may contain false positives or negatives that have not yet been fully audited. Review each report carefully before applying fixes.

Full Name in eslint-plugin-react-x

react-x/immutability

Full Name in @eslint-react/eslint-plugin

@eslint-react/immutability

Features

🧪

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.

Examples

Adding items to an array

Array methods like push() mutate the original array in place. React cannot detect this change because the array reference remains the same.

// Problem: Mutating the array directly
function Component() {
  const [items, setItems] = useState([1, 2, 3]);
  const addItem = () => {
    items.push(4); // Directly mutating the original array
    // ^^^ Do not call 'push()' on 'items'. Props and state are immutable — create a new array instead.
    setItems(items); // Same reference, won't trigger a re-render
  };
}
// Recommended: Create a new array
function Component() {
  const [items, setItems] = useState([1, 2, 3]);
  const addItem = () => {
    setItems([...items, 4]); // New array
  };
}

Updating object properties

Directly assigning to an object's property mutates the existing object. React compares object references, not their contents, so the update is ignored.

// Problem: Mutating object properties directly
function Component() {
  const [user, setUser] = useState({ name: "Alice" });
  const updateName = () => {
    user.name = "Bob"; // Directly mutating the original object
    // ^^^ Do not mutate 'user' directly. Props and state are immutable — create a new object instead.
    setUser(user); // Same reference
  };
}
// Recommended: Create a new object
function Component() {
  const [user, setUser] = useState({ name: "Alice" });
  const updateName = () => {
    setUser({ ...user, name: "Bob" }); // New object
  };
}

Sorting or reversing lists

Array.prototype.sort() and Array.prototype.reverse() mutate the array in place. If you call them directly on state, you mutate React's internal snapshot.

// Problem: sort mutates the original array
function Component() {
  const [items, setItems] = useState([3, 1, 2]);
  const sortItems = () => {
    setItems(items.sort()); // sort mutates the original array
  };
}
// Recommended: Spread into a new array before sorting
function Component() {
  const [items, setItems] = useState([3, 1, 2]);
  const sortItems = () => {
    setItems([...items].sort()); // New array, then sort
  };
}

Updating nested objects

Mutating nested properties doesn't change the top-level object reference, so React won't detect the update at any level.

// Problem: Mutating a nested object
function UserProfile() {
  const [user, setUser] = useState({
    name: "Alice",
    settings: { theme: "light", notifications: true },
  });
  const toggleTheme = () => {
    user.settings.theme = "dark"; // Mutating a nested property
    setUser(user); // Same object reference
  };
}
// Recommended: Create new objects at every level
function UserProfile() {
  const [user, setUser] = useState({
    name: "Alice",
    settings: { theme: "light", notifications: true },
  });
  const toggleTheme = () => {
    setUser({
      ...user,
      settings: {
        ...user.settings,
        theme: "dark",
      },
    });
  };
}

Mutating props directly

Props are owned by the parent component. A child should never modify its props.

// Problem: Mutating props directly
function Component(props) {
  props.name = "Bob"; // Child components should not mutate props
  // ^^^ Do not mutate 'props' directly. Props and state are immutable — create a new object instead.
  return <div>{props.name}</div>;
}
// Recommended: Notify the parent via a callback
function Component({ name, onNameChange }) {
  return <button onClick={() => onNameChange("Bob")}>{name}</button>;
}

Complete component examples

Real components often mix state updates with JSX. Here are fuller examples showing how mutations silently break rendering in complete component contexts.

// Problem: Mutating the array in a list component
function TodoList() {
  const [todos, setTodos] = useState([]);
  const addTodo = (id, text) => {
    todos.push({ id, text }); // Directly mutating the original array
    setTodos(todos); // Same reference, won't trigger a re-render
  };
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}
// Recommended: Create a new array so React detects the change
function TodoList() {
  const [todos, setTodos] = useState([]);
  const addTodo = (id, text) => {
    setTodos([...todos, { id, text }]); // New array
  };
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.text}</li>
      ))}
    </ul>
  );
}
// Problem: sort mutates the state array inside a button handler
function SortedList() {
  const [items, setItems] = useState([3, 1, 2]);
  const sort = () => {
    setItems(items.sort()); // items is mutated!
  };
  return <button onClick={sort}>Sort</button>;
}
// Recommended: 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>;
}

Options

Shared Settings

Custom state hooks can also be configured via shared ESLint settings, which apply consistently across all rules in the plugin:

{
  "settings": {
    "react-x": {
      "additionalStateHooks": "/^(useMyState|useCustomState)$/u"
    }
  }
}

Versions

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