Пошук уроків, статей та іншого контенту
Розберемо додавання, видалення й редагування елементів масивів у State через створення нових масивів.
У React масив у state потрібно розглядати як незмінне значення. Це означає, що під час додавання, видалення або редагування елементів ми не змінюємо наявний масив, а створюємо новий.
React порівнює попереднє і нове значення state. Якщо повернути новий масив, React помітить зміну й повторно відрендерить компонент.
const [items, setItems] = useState([]);У цьому прикладі items — поточний масив, а setItems — функція для його оновлення.
Для додавання елемента використовують оператор розгортання ...:
setItems((previousItems) => [
...previousItems,
newItem,
]);Такий запис:
копіює всі елементи з попереднього масиву;
додає новий елемент у кінець;
створює новий масив.
Наприклад:
const addItem = () => {
const newItem = {
id: Date.now(),
title: "Вивчити React",
};
setItems((previousItems) => [
...previousItems,
newItem,
]);
};Функціональна форма оновлення:
setItems((previousItems) => ...)особливо корисна, коли нове значення залежить від попереднього. React може обробляти кілька оновлень state не одразу, тому використання актуального попереднього значення є надійнішим.
Щоб додати елемент на початок масиву, потрібно змінити порядок:
setItems((previousItems) => [
newItem,
...previousItems,
]);Для видалення елементів зазвичай використовують метод filter. Він повертає новий масив, до якого потрапляють лише елементи, що відповідають умові.
const removeItem = (id) => {
setItems((previousItems) =>
previousItems.filter((item) => item.id !== id)
);
};У цьому прикладі з нового масиву виключається елемент із потрібним id.
Метод filter не змінює початковий масив:
const numbers = [1, 2, 3];
const filteredNumbers = numbers.filter((number) => number !== 2);
console.log(numbers); // [1, 2, 3]
console.log(filteredNumbers); // [1, 3]Для редагування конкретного елемента зручно використовувати map.
Метод map створює новий масив. Для потрібного елемента можна повернути оновлену копію об'єкта, а для інших — залишити елементи без змін.
const updateItem = (id, newTitle) => {
setItems((previousItems) =>
previousItems.map((item) => {
if (item.id === id) {
return {
...item,
title: newTitle,
};
}
return item;
})
);
};Оператор ...item копіює всі властивості об'єкта. Після цього властивість title замінюється новим значенням.
Той самий код можна записати коротше:
const updateItem = (id, newTitle) => {
setItems((previousItems) =>
previousItems.map((item) =>
item.id === id
? { ...item, title: newTitle }
: item
)
);
};Нижче наведено компонент, у якому можна:
додавати завдання;
видаляти завдання;
редагувати текст завдання;
позначати завдання як виконане.
import { useState } from "react";
function TodoList() {
const [todos, setTodos] = useState([
{
id: 1,
title: "Вивчити масиви у state",
completed: false,
},
]);
const [newTitle, setNewTitle] = useState("");
const [editingId, setEditingId] = useState(null);
const [editingTitle, setEditingTitle] = useState("");
const addTodo = (event) => {
event.preventDefault();
const title = newTitle.trim();
if (!title) {
return;
}
const newTodo = {
id: Date.now(),
title,
completed: false,
};
setTodos((previousTodos) => [
...previousTodos,
newTodo,
]);
setNewTitle("");
};
const removeTodo = (id) => {
setTodos((previousTodos) =>
previousTodos.filter((todo) => todo.id !== id)
);
};
const toggleTodo = (id) => {
setTodos((previousTodos) =>
previousTodos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);
};
const startEditing = (todo) => {
setEditingId(todo.id);
setEditingTitle(todo.title);
};
const saveTodo = (id) => {
const title = editingTitle.trim();
if (!title) {
return;
}
setTodos((previousTodos) =>
previousTodos.map((todo) =>
todo.id === id
? { ...todo, title }
: todo
)
);
setEditingId(null);
setEditingTitle("");
};
const cancelEditing = () => {
setEditingId(null);
setEditingTitle("");
};
return (
<div>
<h1>Список завдань</h1>
<form onSubmit={addTodo}>
<input
value={newTitle}
onChange={(event) => setNewTitle(event.target.value)}
placeholder="Нове завдання"
/>
<button type="submit">Додати</button>
</form>
<ul>
{todos.map((todo) => (
<li key={todo.id}>
{editingId === todo.id ? (
<>
<input
value={editingTitle}
onChange={(event) =>
setEditingTitle(event.target.value)
}
/>
<button onClick={() => saveTodo(todo.id)}>
Зберегти
</button>
<button onClick={cancelEditing}>
Скасувати
</button>
</>
) : (
<>
<label>
<input
type="checkbox"
checked={todo.completed}
onChange={() => toggleTodo(todo.id)}
/>
<span
style={{
textDecoration: todo.completed
? "line-through"
: "none",
}}
>
{todo.title}
</span>
</label>
<button onClick={() => startEditing(todo)}>
Редагувати
</button>
<button onClick={() => removeTodo(todo.id)}>
Видалити
</button>
</>
)}
</li>
))}
</ul>
</div>
);
}
export default TodoList;Додавання:
setTodos((previousTodos) => [
...previousTodos,
newTodo,
]);Видалення:
setTodos((previousTodos) =>
previousTodos.filter((todo) => todo.id !== id)
);Редагування:
setTodos((previousTodos) =>
previousTodos.map((todo) =>
todo.id === id
? { ...todo, title: newTitle }
: todo
)
);Зміна вкладеної властивості:
setTodos((previousTodos) =>
previousTodos.map((todo) =>
todo.id === id
? { ...todo, completed: !todo.completed }
: todo
)
);В усіх випадках створюється новий масив. Під час редагування також створюється новий об'єкт для зміненого елемента.
Такий код змінює наявний масив:
todos.push(newTodo);
setTodos(todos);Проблема в тому, що посилання на масив залишилося тим самим. React може не розпізнати таку зміну як оновлення.
Також не варто змінювати об'єкт безпосередньо:
todo.title = "Нова назва";Замість цього потрібно створити новий масив і нову копію зміненого об'єкта:
setTodos((previousTodos) =>
previousTodos.map((todo) =>
todo.id === id
? { ...todo, title: "Нова назва" }
: todo
)
);| Операція | Метод або синтаксис | | --- | --- | | Додати в кінець | [...items, newItem] | | Додати на початок | [newItem, ...items] | | Видалити | filter() | | Редагувати | map() | | Замінити весь масив | setItems(newItems) |
Незалежно від операції, результатом має бути новий масив.
pushitems.push(newItem);
setItems(items);push змінює початковий масив. Використовуйте оператор розгортання:
setItems((previousItems) => [
...previousItems,
newItem,
]);splicesplice також змінює початковий масив:
items.splice(index, 1);
setItems(items);Для видалення використовуйте filter:
setItems((previousItems) =>
previousItems.filter((item) => item.id !== itemId)
);items[0].title = "Нове значення";
setItems([...items]);Зовнішній масив тут справді новий, але вкладений об'єкт було змінено напряму. Краще створити копію потрібного об'єкта через map:
setItems((previousItems) =>
previousItems.map((item) =>
item.id === itemId
? { ...item, title: "Нове значення" }
: item
)
);Якщо новий масив залежить від попереднього, не покладайтеся на змінну, яка була прочитана раніше:
setItems([...items, newItem]);Надійніший варіант:
setItems((previousItems) => [
...previousItems,
newItem,
]);keyПід час виведення масиву кожен елемент повинен мати стабільний key:
{items.map((item) => (
<li key={item.id}>{item.title}</li>
))}Для key краще використовувати унікальний ідентифікатор елемента, а не індекс масиву, особливо якщо елементи можна видаляти або редагувати.
Масиви в state не потрібно змінювати напряму.
Для додавання створюйте масив за допомогою ....
Для видалення використовуйте filter.
Для редагування використовуйте map.
Під час редагування об'єкта створюйте його копію через ...object.
Якщо нове значення залежить від попереднього, використовуйте функціональну форму setState.
Кожен елемент списку повинен мати стабільний key.