Пошук уроків, статей та іншого контенту
Передаватимете обробники подій через props і реалізуєте взаємодію дочірніх компонентів із батьківськими.
У React дані зазвичай передаються від батьківського компонента до дочірнього через props. Це стосується не лише значень, а й функцій.
Передавши функцію дочірньому компоненту, батьківський компонент може надати йому спосіб повідомляти про події або запускати зміни стану.
Такий підхід часто називають callback prop — властивістю, що містить функцію зворотного виклику.
Дочірній компонент не повинен напряму змінювати стан батьківського компонента. Натомість батько:
зберігає стан;
створює функцію для зміни цього стану;
передає функцію дочірньому компоненту через props;
дочірній компонент викликає функцію у відповідь на дію користувача.
Це зберігає односпрямований потік даних:
батьківський компонент
↓ props
дочірній компонент
↑ виклик функціїРозглянемо простий приклад:
function Button({ onClick }) {
return <button onClick={onClick}>Натиснути</button>;
}
function App() {
function handleClick() {
console.log('Кнопку натиснуто');
}
return <Button onClick={handleClick} />;
}
export default App;У цьому прикладі:
App визначає функцію handleClick;
функція передається компоненту Button через prop onClick;
Button використовує її як обробник події;
під час натискання кнопки виконується код із handleClick.
Назва onClick не є спеціальною для власних props. Можна використати будь-яку зрозумілу назву, наприклад onSave, onRemove або onSelect.
Функцію потрібно передавати без круглих дужок:
<Button onClick={handleClick} />Так передається саме функція, і дочірній компонент зможе викликати її пізніше.
Якщо написати так:
<Button onClick={handleClick()} />функція виконається одразу під час рендерингу App. Результат її виконання буде передано як prop замість самої функції.
Якщо потрібно передати аргумент, використовуйте стрілкову функцію:
<Button onClick={() => handleClick(10)} />У цьому випадку handleClick(10) виконається лише після натискання кнопки.
Props передаються лише зверху вниз. Проте дочірній компонент може повідомити батьківський про подію, викликавши передану функцію.
function CounterButton({ onIncrement }) {
return (
<button onClick={onIncrement}>
Збільшити
</button>
);
}
function Counter() {
const [count, setCount] = React.useState(0);
function handleIncrement() {
setCount((currentCount) => currentCount + 1);
}
return (
<div>
<p>Значення: {count}</p>
<CounterButton onIncrement={handleIncrement} />
</div>
);
}Компонент CounterButton не знає, де зберігається значення лічильника і як воно змінюється. Він лише викликає onIncrement.
Стан залишається в Counter, тому саме батьківський компонент контролює дані.
Функція, передана через props, може приймати аргументи. Це дає змогу дочірньому компоненту передавати батьківському додаткові дані.
function ProductItem({ product, onAddToCart }) {
return (
<li>
<span>{product.name}</span>
<button onClick={() => onAddToCart(product.id)}>
Додати
</button>
</li>
);
}Під час натискання дочірній компонент передає product.id у функцію onAddToCart.
Батьківський компонент може обробити це значення:
function ProductList() {
const products = [
{ id: 1, name: 'Клавіатура' },
{ id: 2, name: 'Миша' },
];
function handleAddToCart(productId) {
console.log(`Товар ${productId} додано до кошика`);
}
return (
<ul>
{products.map((product) => (
<ProductItem
key={product.id}
product={product}
onAddToCart={handleAddToCart}
/>
))}
</ul>
);
}Тут:
ProductList передає товар і callback;
ProductItem відображає дані;
ProductItem передає ідентифікатор товару назад через виклик callback;
ProductList вирішує, що робити з цим ідентифікатором.
У наступному прикладі батьківський компонент зберігає список завдань. Дочірній компонент відображає одне завдання і повідомляє батьківський компонент, коли користувач хоче його видалити.
import { useState } from 'react';
function TaskItem({ task, onRemove }) {
return (
<li>
<span>{task.title}</span>
<button type="button" onClick={() => onRemove(task.id)}>
Видалити
</button>
</li>
);
}
function TaskList({ tasks, onRemove }) {
return (
<ul>
{tasks.map((task) => (
<TaskItem
key={task.id}
task={task}
onRemove={onRemove}
/>
))}
</ul>
);
}
export default function App() {
const [tasks, setTasks] = useState([
{ id: 1, title: 'Вивчити props' },
{ id: 2, title: 'Створити callback' },
{ id: 3, title: 'Попрактикуватися з подіями' },
]);
function handleRemoveTask(taskId) {
setTasks((currentTasks) =>
currentTasks.filter((task) => task.id !== taskId)
);
}
return (
<main>
<h1>Мої завдання</h1>
<TaskList
tasks={tasks}
onRemove={handleRemoveTask}
/>
</main>
);
}App зберігає масив tasks у стані.
App передає масив у TaskList.
App передає функцію handleRemoveTask у TaskList.
TaskList передає цю функцію кожному TaskItem.
TaskItem викликає onRemove(task.id) після натискання кнопки.
App фільтрує стан і видаляє потрібне завдання.
Після оновлення стану React повторно рендерить список.
Функція handleRemoveTask використовує функціональну форму оновлення стану:
setTasks((currentTasks) => {
return currentTasks.filter((task) => task.id !== taskId);
});Це дає змогу працювати з актуальним попереднім значенням стану.
Для функцій, які реагують на події, часто використовують префікс on:
onClick;
onSubmit;
onChange;
onSelect;
onRemove;
onToggle;
onSave.
Назва має описувати подію або дію, а не внутрішню реалізацію:
<UserForm onSubmit={handleUserSubmit} />Краще, ніж:
<UserForm onFunction={handleUserSubmit} />У батьківському компоненті обробник можна назвати з префіксом handle:
function handleUserSubmit(userData) {
// Обробка даних форми
}Так із назви зрозуміло, де описана подія, а де передається callback.
Компонент може отримувати кілька функцій через props:
function Editor({ onSave, onCancel }) {
return (
<div>
<button type="button" onClick={onSave}>
Зберегти
</button>
<button type="button" onClick={onCancel}>
Скасувати
</button>
</div>
);
}Батьківський компонент сам вирішує, що має відбутися після кожної дії:
function App() {
function handleSave() {
console.log('Дані збережено');
}
function handleCancel() {
console.log('Редагування скасовано');
}
return (
<Editor
onSave={handleSave}
onCancel={handleCancel}
/>
);
}Дочірній компонент відповідає за інтерфейс, а батьківський — за логіку застосунку, якщо стан цієї логіки належить батькові.
React передає об’єкт події в обробник автоматично:
function SearchInput({ onSearch }) {
function handleChange(event) {
onSearch(event.target.value);
}
return (
<input
type="search"
placeholder="Пошук"
onChange={handleChange}
/>
);
}У цьому прикладі SearchInput не зберігає текст пошуку. Він передає поточне значення батьківському компоненту:
function App() {
function handleSearch(value) {
console.log('Пошуковий запит:', value);
}
return <SearchInput onSearch={handleSearch} />;
}Зазвичай дочірній компонент може передати батькові вже потрібне значення, а не весь об’єкт події. Так API компонента стає зрозумілішим:
onSearch(event.target.value)замість:
onSearch(event)Іноді функція може бути необов’язковою. Тоді перед викликом потрібно перевірити, чи вона існує.
function Panel({ onClose }) {
function handleClose() {
if (onClose) {
onClose();
}
}
return (
<button type="button" onClick={handleClose}>
Закрити
</button>
);
}Для короткого запису можна використати optional chaining:
function Panel({ onClose }) {
return (
<button type="button" onClick={() => onClose?.()}>
Закрити
</button>
);
}Якщо onClose не передано, виклик не спричинить помилку.
Props доступні лише для читання. Не змінюйте їх напряму:
function TaskItem({ task }) {
task.title = 'Нове значення';
return <span>{task.title}</span>;
}Замість цього передайте callback батьківському компоненту, а зміни виконуйте там, де зберігається стан.
Не змінюйте масив методом push:
function handleAddTask(newTask) {
tasks.push(newTask);
setTasks(tasks);
}Створіть новий масив:
function handleAddTask(newTask) {
setTasks((currentTasks) => [
...currentTasks,
newTask,
]);
}Помилково:
<button onClick={handleRemove(task.id)}>
Видалити
</button>Правильно:
<button onClick={() => handleRemove(task.id)}>
Видалити
</button>У першому варіанті функція викликається під час рендерингу, а не після натискання.
thisУ функціональних компонентах не потрібно використовувати this.props:
function Button(props) {
return <button onClick={this.props.onClick}>Натиснути</button>;
}Правильно:
function Button({ onClick }) {
return <button onClick={onClick}>Натиснути</button>;
}Якщо батько передає onRemove, дочірній компонент має отримувати саме onRemove:
function App() {
return <TaskItem onRemove={handleRemove} />;
}
function TaskItem({ onDelete }) {
return <button onClick={onDelete}>Видалити</button>;
}У цьому прикладі onDelete буде undefined, тому обробник не працюватиме. Назви мають збігатися:
function TaskItem({ onRemove }) {
return (
<button type="button" onClick={onRemove}>
Видалити
</button>
);
}Функції можна передавати дочірнім компонентам через props.
Дочірній компонент може викликати передану функцію у відповідь на подію.
Так дочірній компонент повідомляє батьківський про дію користувача.
Стан зазвичай зберігається в батьківському компоненті, який передає callback для його зміни.
Функцію передають без виклику: onClick={handleClick}.
Якщо потрібні аргументи, використовуйте обгортку: onClick={() => handleClick(id)}.
Для callback props зручно використовувати назви з префіксом on: onSave, onRemove, onSelect.
Props не можна змінювати напряму. Усі зміни стану мають виконуватися через відповідні функції.