Пошук уроків, статей та іншого контенту
Освоїте useState для зберігання стану компонента, його оновлення та роботи з функціональними оновленнями.
Стан — це дані компонента, які можуть змінюватися під час його роботи. Коли стан змінюється, React повторно рендерить компонент і показує актуальні значення в інтерфейсі.
Для зберігання стану у функціональних компонентах використовують хук useState.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Лічильник: {count}</p>
<button onClick={() => setCount(count + 1)}>
Збільшити
</button>
</div>
);
}
export default Counter;У цьому прикладі:
count — поточне значення стану;
setCount — функція для оновлення стану;
useState(0) — створення стану з початковим значенням 0.
useStateВиклик useState повертає масив із двох елементів:
const [state, setState] = useState(initialValue);Зазвичай використовують деструктуризацію:
const [name, setName] = useState("Іван");Тут:
name містить поточне значення;
setName змінює значення;
"Іван" є початковим значенням.
Назви можуть бути будь-якими, але зазвичай функцію оновлення називають із префіксом set:
const [isOpen, setIsOpen] = useState(false);
const [items, setItems] = useState([]);
const [user, setUser] = useState(null);Щоб змінити стан, потрібно викликати функцію-оновлювач:
setCount(10);Після цього React запланує повторний рендер компонента. Під час нового рендера змінна count отримає нове значення.
import { useState } from "react";
function Greeting() {
const [name, setName] = useState("Гість");
return (
<div>
<p>Вітаємо, {name}!</p>
<button onClick={() => setName("Олена")}>
Увійти як Олена
</button>
</div>
);
}
export default Greeting;Не потрібно вручну викликати метод на кшталт render. React сам оновить інтерфейс після зміни стану.
Компонент може мати будь-яку кількість незалежних станів:
import { useState } from "react";
function Profile() {
const [name, setName] = useState("Гість");
const [isOnline, setIsOnline] = useState(false);
return (
<section>
<h2>{name}</h2>
<p>{isOnline ? "Онлайн" : "Офлайн"}</p>
<button onClick={() => setName("Марія")}>
Змінити ім'я
</button>
<button onClick={() => setIsOnline(!isOnline)}>
Змінити статус
</button>
</section>
);
}
export default Profile;Кожен виклик useState створює окрему пару:
значення стану;
функцію для його оновлення.
Найчастіше стан оновлюють у функціях-обробниках подій:
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleIncrement() {
setCount(count + 1);
}
function handleDecrement() {
setCount(count - 1);
}
return (
<div>
<p>Поточне значення: {count}</p>
<button onClick={handleDecrement}>−1</button>
<button onClick={handleIncrement}>+1</button>
</div>
);
}
export default Counter;Обробник передають у onClick без виклику:
<button onClick={handleIncrement}>+1</button>Не слід писати так:
<button onClick={handleIncrement()}>+1</button>У такому разі функція виконається під час рендера, а не після натискання кнопки.
Якщо нове значення залежить від попереднього, використовуйте функціональне оновлення:
setCount((previousCount) => previousCount + 1);Функція отримує актуальне попереднє значення і повертає нове.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
function handleAddThree() {
setCount((previousCount) => previousCount + 1);
setCount((previousCount) => previousCount + 1);
setCount((previousCount) => previousCount + 1);
}
return (
<div>
<p>Значення: {count}</p>
<button onClick={handleAddThree}>
Додати 3
</button>
</div>
);
}
export default Counter;Після натискання значення збільшиться на 3, оскільки кожне оновлення використовує результат попереднього оновлення.
Порівняйте:
// Значення count у межах цього обробника може бути однаковим
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);І:
// Кожне оновлення отримує актуальний попередній стан
setCount((previousCount) => previousCount + 1);
setCount((previousCount) => previousCount + 1);
setCount((previousCount) => previousCount + 1);Функціональна форма особливо важлива, коли:
кілька оновлень виконуються поспіль;
нове значення залежить від попереднього;
оновлення відбувається в асинхронному коді.
Стан може зберігати рядки, наприклад значення поля введення:
import { useState } from "react";
function NameForm() {
const [name, setName] = useState("");
function handleChange(event) {
setName(event.target.value);
}
return (
<form>
<label>
Ваше ім'я:
<input
type="text"
value={name}
onChange={handleChange}
/>
</label>
<p>Введене ім'я: {name || "поки що нічого"}</p>
</form>
);
}
export default NameForm;Значення value={name} пов’язує поле зі станом, а onChange оновлює стан після кожної зміни введеного тексту.
Стан може бути об'єктом. Під час оновлення потрібно створити новий об'єкт, зберігши попередні властивості:
import { useState } from "react";
function UserForm() {
const [user, setUser] = useState({
name: "",
age: "",
});
function handleNameChange(event) {
setUser((previousUser) => ({
...previousUser,
name: event.target.value,
}));
}
function handleAgeChange(event) {
setUser((previousUser) => ({
...previousUser,
age: event.target.value,
}));
}
return (
<form>
<input
type="text"
placeholder="Ім'я"
value={user.name}
onChange={handleNameChange}
/>
<input
type="number"
placeholder="Вік"
value={user.age}
onChange={handleAgeChange}
/>
<p>
{user.name || "Без імені"}, {user.age || "вік не вказано"}
</p>
</form>
);
}
export default UserForm;Оператор ...previousUser копіює попередні властивості. Без нього під час зміни name властивість age була б втрачена.
Для масивів також потрібно створювати новий масив, а не змінювати існуючий:
import { useState } from "react";
function TodoList() {
const [todos, setTodos] = useState([]);
function addTodo() {
const newTodo = {
id: Date.now(),
text: `Завдання ${todos.length + 1}`,
};
setTodos((previousTodos) => [
...previousTodos,
newTodo,
]);
}
return (
<div>
<button onClick={addTodo}>Додати завдання</button>
<ul>
{todos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</div>
);
}
export default TodoList;Оператор ...previousTodos створює новий масив із попередніми елементами та додає новий елемент у кінець.
useStateІмпортуйте useState з React:
import { useState } from "react";Викликайте useState лише всередині функціонального компонента або іншого хука.
Не змінюйте стан напряму:
// Неправильно
count = count + 1;Використовуйте функцію-оновлювач:
// Правильно
setCount((previousCount) => previousCount + 1);Якщо нове значення залежить від попереднього, використовуйте функціональне оновлення.
Для об'єктів і масивів створюйте нове значення замість зміни старого.
function handleClick() {
setCount(count + 1);
console.log(count);
}console.log може вивести старе значення, оскільки поточний рендер ще не змінився. Нове значення буде доступне під час наступного рендера.
// Неправильно
user.name = "Олена";
setUser(user);Створіть новий об'єкт:
// Правильно
setUser((previousUser) => ({
...previousUser,
name: "Олена",
}));// Неправильно
todos.push(newTodo);
setTodos(todos);Створіть новий масив:
// Правильно
setTodos((previousTodos) => [
...previousTodos,
newTodo,
]);// Неправильно: функція викликається одразу
<button onClick={setCount(count + 1)}>
Збільшити
</button>Передайте функцію:
// Правильно
<button onClick={() => setCount(count + 1)}>
Збільшити
</button>Або:
function handleIncrement() {
setCount((previousCount) => previousCount + 1);
}
<button onClick={handleIncrement}>
Збільшити
</button>useState додає функціональному компоненту локальний стан.
Хук повертає поточне значення та функцію для його оновлення.
Після оновлення стану React повторно рендерить компонент.
Для значень, залежних від попереднього стану, використовуйте функціональну форму:
setValue((previousValue) => newValue);Об'єкти й масиви потрібно оновлювати шляхом створення нових значень.
Стан не слід змінювати напряму.