Стан зберігається не «в компоненті», а в позиції компонента в дереві. React зберігає стан, доки на тому самому місці дерева рендериться той самий тип компонента.
{isFancy ? <Counter fancy /> : <Counter />}
Перемикання isFancy не скидає лічильник: на тому самому місці той самий тип Counter, змінилися лише props.
Стан скидається, коли:
- на тому самому місці з'являється інший тип компонента (
<Counter />→<Timer />) або інший тег (<div>→<section>- скидається все піддерево); - компонент прибрано з дерева і повернуто;
- змінився
key.
key для скидання стану - найважливіший практичний прийом:
<ContactForm key={contact.id} contact={contact} />
Перемкнули контакт - змінився key - React створює новий екземпляр форми з чистим станом. Без key форма зберегла б введений текст від попереднього контакту.
Це заміна поширеному антипатерну «скинути стан в ефекті при зміні props»:
// погано: зайвий рендер зі старими даними, потім скидання
useEffect(() => { setDraft(''); }, [contact.id]);
Пастка - компонент, оголошений усередині компонента:
function Page() {
const [text, setText] = useState('');
function Field() { // нова функція на кожному рендері Page
return <input value={text} onChange={(e) => setText(e.target.value)} />;
}
return <Field />;
}
На кожному рендері Field - новий тип компонента, тож React розмонтовує старий і монтує новий: поле губить фокус після кожної літери. Компоненти оголошують на верхньому рівні модуля.
Зворотний бік - стан «прилипає», коли це не очікується: однакова позиція в різних гілках умови, однаковий key у різних списках. Тоді допомагає різний key для гілок.
Зберегти стан прихованого компонента (вкладки, бічна панель) - не прибирати його з дерева: CSS-приховування, <Activity mode="hidden"> або винести стан угору до батька.