Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Підняття стану в React». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**Підняття стану (lifting state up)** - це переміщення спільного стану до найближчого спільного батька компонентів, яким він потрібен. Батько тримає дані, передає їх вниз через props і отримує оновлення через callback props. Сусідні компоненти залишаються синхронізованими, бо рендеряться з одного джерела. ```tsx function Converter() { const [celsius, setCelsius] = useState(0); return ( <> <input value={celsius} onChange={e => setCelsius(+e.target.value)} /> <input value={celsius * 9/5 + 32} onChange={e => setCelsius((+e.target.value - 32) * 5/9)} /> </> ); } ``` **Головне:** дочірні компоненти стають controlled - вони читають з props, а не з локального стану.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**Підняття стану (lifting state up)** - паттерн React, де спільний стан переміщується до найближчого спільного батька компонентів, які його потребують, і передається вниз через props. ## Теорія ### TL;DR - Сусідні компоненти не можуть спілкуватися напряму. Батько тримає дані і передає їх вниз. - Аналогія: двоє дітей, один термостат. Батько тримає ручку і крутить її за запитом. - Коли стан батька змінюється, всі діти отримують свіжі значення одночасно. Локальні стани розходяться. - Підніми, якщо 2+ сусіди потребують однакових даних або один input керує іншим. ### Швидкий приклад ```tsx // ❌ Два inputs, два локальних стани. Вони ніколи не синхронізуються. function CelsiusInput() { const [temp, setTemp] = useState(0); return <input value={temp} onChange={e => setTemp(+e.target.value)} />; } function FahrenheitInput() { const [temp, setTemp] = useState(32); // окремий стан, розходиться return <input value={temp} onChange={e => setTemp(+e.target.value)} />; } // ✅ Батько тримає стан. Діти - controlled components. function TemperatureConverter() { const [celsius, setCelsius] = useState(0); return ( <> <input value={celsius} onChange={e => setCelsius(+e.target.value)} /> <input value={Math.round(celsius * 9 / 5 + 32)} onChange={e => setCelsius((+e.target.value - 32) * 5 / 9)} /> </> ); } // Змінюєш Celsius → Fahrenheit оновлюється одразу. І навпаки. ``` ### Головна відмінність Після підняття дочірні компоненти стають **контрольованими (controlled)**: вони читають з props і викликають батьківські обробники при змінах. Локальний `useState` зникає. Батько ре-рендериться і одночасно надсилає свіжі значення всім дітям, тому вони не можуть розійтися. ### Коли піднімати - 2+ сусіди потребують однакових даних → підніми до найближчого спільного батька - Один input керує іншим (Celsius/Fahrenheit) → підніми і обчислюй похідні значення у батьку - Поля форми потрібно валідувати разом → підніми до форм-контейнера - Список і фільтр читають з одного масиву → підніми масив Якщо стан використовується лише одним компонентом, залиш його локальним. Якщо ти передаєш props на 3+ рівні вглиб просто щоб поділитися даними, це prop drilling. Тоді варто підключити Context або стейт-менеджер. ### Як це працює всередині React Коли `setState` викликається у батьку, React ставить у чергу ре-рендер цього компонента і всього піддерева. Діти отримують свіжі props у фазі рендеру. Якщо значення prop змінилося, React синхронізує DOM через `input.value = newValue`. Всі діти оновлюються за один прохід, тому не можуть розійтися. ### Типові помилки **Поєднання локального стану з пропом** ```tsx // ❌ Локальний стан перекриває prop. Зміни не доходять до батька. function Child({ value, onChange }: { value: number; onChange: (v: number) => void }) { const [local, setLocal] = useState(0); // воює з пропом! return <input value={local} onChange={e => setLocal(+e.target.value)} />; } // ✅ Controlled: використовуй prop напряму function Child({ value, onChange }: { value: number; onChange: (v: number) => void }) { return <input value={value} onChange={e => onChange(+e.target.value)} />; } ``` На практиці це найпоширеніший баг при роботі з цим паттерном. Ти передаєш значення через prop, дитина має локальний `useState`, і зміни ніколи не доходять до батька. React DevTools показує оновлення prop, але input залишається замороженим. **Підняття занадто високо** ```tsx // ❌ App ре-рендериться при кожному натисканні клавіші у формі входу <App formData={formData} setFormData={setFormData}> <LoginForm /> </App> ``` Підноси тільки до найближчого спільного батька. `LoginPage` - правильний рівень, не `App`. Непов'язані компоненти не повинні ре-рендеритися через поле форми. **Мутація стану замість заміни** ```tsx // ❌ Push мутує масив. React бачить той самий reference. Ре-рендеру не буде. const addItem = () => { items.push('new'); setItems(items); }; // ✅ Створюй новий масив const addItem = () => setItems([...items, 'new']); ``` ### Де зустрічається у реальних проектах - Приклад з документації React: конвертер температур Celsius/Fahrenheit - звідси і пішов цей паттерн - TodoMVC: кнопки фільтрів і список todos ділять масив `todos`, піднятий до `App` - Сторінки пошуку в Next.js: стан запиту піднятий над полем вводу і списком результатів - Форми входу: перемикач `showPassword` і поле `password` ділять стан у батьківській формі ### Питання на співбесіді **Q:** Навіщо не використовувати Redux або Context для всього? **A:** Redux потребує шаблонного коду (actions, reducers, store), який не окупається для двох сусідів. Спочатку підніми стан. Context підключай при 5+ споживачах або 3+ рівнях вкладення. Redux доречний для стану, що ділиться між багатьма непов'язаними компонентами по всьому додатку. **Q:** Як дочірній компонент може передати дані батьку? **A:** Через callback prop: `onValueChange(data)`. Дитина викликає його, батько оновлює свій стан. Ось і весь механізм. **Q:** Чи викликає підняття стану проблеми з продуктивністю? **A:** Рідко. React групує оновлення стану в обробниках подій. Якщо конкретне піддерево ре-рендериться занадто часто, загорни стабільні дочірні компоненти в `React.memo`. Спочатку виміряй через React DevTools Profiler, потім оптимізуй. **Q:** (Senior) Коли підняття стану переростає у prop drilling? Як мігрувати? **A:** Приблизно при 10+ полях або 3+ рівнях вкладення. Витягни хук `useForm`, що повертає `{ values, updateField }`. Загорни піддерево в `<FormContext.Provider>` і читай через `useContext`. Стан все ще живе в одному місці, але компоненти отримують доступ без ланцюжків props. ## Приклади ### Базовий: конвертер температур ```tsx function TemperatureConverter() { const [celsius, setCelsius] = useState(0); const toFahrenheit = (c: number) => Math.round(c * 9 / 5 + 32); const toCelsius = (f: number) => (f - 32) * 5 / 9; return ( <div> <label> Celsius: <input type="number" value={celsius} onChange={e => setCelsius(+e.target.value)} /> </label> <label> Fahrenheit: <input type="number" value={toFahrenheit(celsius)} onChange={e => setCelsius(toCelsius(+e.target.value))} /> </label> </div> ); } // Вводиш 100 у Celsius → Fahrenheit показує 212 одразу // Вводиш 32 у Fahrenheit → Celsius показує 0 одразу ``` Один компонент, одне джерело даних. Обидва inputs читають з `celsius` і записують через функції конвертації. ### Середній рівень: форма входу зі спільним станом ```tsx function LoginForm() { const [formData, setFormData] = useState({ email: '', password: '', showPassword: false, }); const updateField = (field: keyof typeof formData) => (e: React.ChangeEvent<HTMLInputElement>) => setFormData({ ...formData, [field]: e.target.value }); return ( <form> <input value={formData.email} onChange={updateField('email')} placeholder="Email" /> <input type={formData.showPassword ? 'text' : 'password'} value={formData.password} onChange={updateField('password')} /> <label> <input type="checkbox" checked={formData.showPassword} onChange={e => setFormData({ ...formData, showPassword: e.target.checked })} /> Показати пароль </label> </form> ); } // Ставиш галочку → поле пароля перемикається між text і password // Всі три поля ділять один об'єкт стану на рівні форми ``` Чекбокс і поле пароля - сусіди. Без підняття чекбокс не міг би впливати на тип поля пароля. Форма тримає все, тому координація тривіальна.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.