Перейти к содержанию

set-state-in-render

Проверяет, что состояние не устанавливается во время рендера безусловно: это запускает дополнительные рендеры и может привести к бесконечному циклу рендера.

Подробности правила

Безусловный вызов setState во время рендера запускает ещё один рендер до того, как текущий завершится. Получается бесконечный цикл, и приложение падает.

Типичные нарушения

Неверно

1
2
3
4
5
6
// ❌ Unconditional setState directly in render
function Component({value}) {
  const [count, setCount] = useState(0);
  setCount(value); // Infinite loop!
  return <div>{count}</div>;
}

Верно

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// ✅ Derive during render
function Component({items}) {
  const sorted = [...items].sort(); // Just calculate it in render
  return <ul>{sorted.map(/*...*/)}</ul>;
}

// ✅ Set state in event handler
function Component() {
  const [count, setCount] = useState(0);
  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

// ✅ Derive from props instead of setting state
function Component({user}) {
  const name = user?.name || '';
  const email = user?.email || '';
  return <div>{name}</div>;
}

// ✅ Conditionally derive state from props and state from previous renders
function Component({ items }) {
  const [isReverse, setIsReverse] = useState(false);
  const [selection, setSelection] = useState(null);

  const [prevItems, setPrevItems] = useState(items);
  if (items !== prevItems) { // This condition makes it valid
    setPrevItems(items);
    setSelection(null);
  }
  // ...
}

Устранение неполадок

Нужно синхронизировать состояние с пропом

Частая проблема — попытка «поправить» состояние после рендера. Допустим, счётчик не должен превышать проп max:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ❌ Wrong: clamps during render
function Counter({max}) {
  const [count, setCount] = useState(0);

  if (count > max) {
    setCount(max);
  }

  return (
    <button onClick={() => setCount(count + 1)}>
      {count}
    </button>
  );
}

Как только count превысит max, запустится бесконечный цикл.

Чаще эту логику лучше перенести в событие — туда, где состояние устанавливается в первый раз. Например, максимум можно проверять в момент обновления состояния:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// ✅ Clamp when updating
function Counter({max}) {
  const [count, setCount] = useState(0);

  const increment = () => {
    setCount(current => Math.min(current + 1, max));
  };

  return <button onClick={increment}>{count}</button>;
}

Теперь сеттер выполняется только в ответ на клик, React нормально завершает рендер, и count никогда не переходит за max.

В редких случаях состояние нужно поправить по данным из предыдущих рендеров. Тогда следуйте этому паттерну условной установки состояния.

Источник — https://react.dev/reference/eslint-plugin-react-hooks/lints/set-state-in-render

Комментарии