Пошук уроків, статей та іншого контенту
Реалізуєте умовний рендеринг і відображення масивів у JSX за допомогою списків та ключів.
У React інтерфейс часто залежить від стану або властивостей компонента:
користувач авторизований чи ні;
список завдань порожній чи містить елементи;
дані ще завантажуються;
сталася помилка;
елемент має певний статус.
JSX не має окремого синтаксису для if усередині розмітки, але умовний рендеринг можна реалізувати за допомогою звичайних конструкцій JavaScript.
if перед JSXКоли логіка достатньо складна, зручно підготувати змінну або повернути різний JSX до основної розмітки:
function UserGreeting({ user }) {
if (!user) {
return <p>Будь ласка, увійдіть у систему.</p>;
}
return <p>Вітаємо, {user.name}!</p>;
}Якщо user має значення, компонент повертає привітання. Якщо user дорівнює null або undefined, відображається повідомлення для неавторизованого користувача.
Для короткого вибору між двома варіантами використовується тернарний оператор:
function SaveButton({ isSaving }) {
return (
<button type="button" disabled={isSaving}>
{isSaving ? "Збереження..." : "Зберегти"}
</button>
);
}Загальний синтаксис:
умова ? значенняЯкщоТак : значенняЯкщоНіТернарний оператор можна використовувати не лише для тексту, а й для цілих JSX-елементів:
function AuthPanel({ isLoggedIn }) {
return (
<section>
{isLoggedIn ? (
<button type="button">Вийти</button>
) : (
<button type="button">Увійти</button>
)}
</section>
);
}Якщо вкладених тернарних операторів стає багато, код важко читати. У такому випадку краще винести логіку в окрему функцію або використати if перед return.
&&Якщо елемент потрібно показати лише за виконання умови, можна використати &&:
function Notifications({ count }) {
return (
<header>
<h1>Панель керування</h1>
{count > 0 && <span>Нових сповіщень: {count}</span>}
</header>
);
}Якщо count > 0 має значення true, React відрендерить span. Якщо умова має значення false, React нічого не відобразить.
Однак потрібно враховувати особливість числових значень:
function ItemCount({ count }) {
return <p>{count && "Є елементи"}</p>;
}Коли count дорівнює 0, React може відобразити 0, тому що 0 є допустимим значенням для виведення. Для чисел краще явно перевіряти умову:
function ItemCount({ count }) {
return <p>{count > 0 && "Є елементи"}</p>;
}Щоб нічого не відрендерити, компонент може повернути null:
function ErrorMessage({ error }) {
if (!error) {
return null;
}
return <p role="alert">{error}</p>;
}Повернення null не створює елемент у DOM.
Для перетворення масиву на набір JSX-елементів зазвичай використовується метод map():
function ProductList({ products }) {
return (
<ul>
{products.map((product) => (
<li key={product.id}>
{product.name}: {product.price} грн
</li>
))}
</ul>
);
}map() викликає передану функцію для кожного елемента масиву та повертає новий масив. React може відобразити цей масив JSX-елементів усередині батьківського елемента.
Об’єкт кожного товару в прикладі має таку структуру:
{
id: 1,
name: "Клавіатура",
price: 2400
}Перед відображенням списку можна перевірити, чи містить масив елементи:
function TaskList({ tasks }) {
if (tasks.length === 0) {
return <p>Завдань немає.</p>;
}
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}Такий підхід зручний, коли порожній список має окремий повноцінний стан.
Інший варіант — використати тернарний оператор безпосередньо в JSX:
function TaskList({ tasks }) {
return (
<section>
{tasks.length > 0 ? (
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
) : (
<p>Завдань немає.</p>
)}
</section>
);
}Кожен елемент списку, який повертається з map(), має отримати унікальний проп key:
function Users({ users }) {
return (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
}key допомагає React зрозуміти:
який елемент було додано;
який елемент видалено;
який елемент змінився;
який елемент залишився на тому самому місці.
key потрібен React для порівняння попереднього та нового дерева компонентів. Він не передається компоненту як звичайний проп.
function Task({ task }) {
return <li>{task.title}</li>;
}
function TaskList({ tasks }) {
return (
<ul>
{tasks.map((task) => (
<Task key={task.id} task={task} />
))}
</ul>
);
}У компоненті Task значення key недоступне через props.key. Якщо компоненту потрібен ідентифікатор, передайте його окремим пропом:
function Task({ task, taskId }) {
return <li data-task-id={taskId}>{task.title}</li>;
}
function TaskList({ tasks }) {
return (
<ul>
{tasks.map((task) => (
<Task key={task.id} task={task} taskId={task.id} />
))}
</ul>
);
}Найкращий ключ — стабільний унікальний ідентифікатор, який належить самому елементу:
<li key={task.id}>{task.title}</li>Ключ має залишатися однаковим для того самого елемента під час наступних рендерів. Не варто створювати ключ заново під час кожного рендеру:
// Погано: значення змінюється під час кожного рендеру
<li key={Math.random()}>{task.title}</li>Якщо елементи масиву не мають ідентифікаторів, інколи можна використати індекс:
function Colors({ colors }) {
return (
<ul>
{colors.map((color, index) => (
<li key={index}>{color}</li>
))}
</ul>
);
}Це прийнятно для статичного списку, який не сортується, не фільтрується та не змінює порядок. Для списків, де елементи додаються, видаляються або переміщуються, індекс може призвести до помилкового збереження стану компонентів. У таких випадках потрібен стабільний id.
У наступному прикладі:
відображається заголовок панелі;
показується індикатор завантаження;
відображається повідомлення про помилку;
виводиться список завдань;
для кожного завдання використовується стабільний ключ;
завершені завдання мають інший вигляд.
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import "./styles.css";
const tasks = [
{
id: 101,
title: "Підготувати макет сторінки",
completed: true,
},
{
id: 102,
title: "Реалізувати список завдань",
completed: false,
},
{
id: 103,
title: "Додати перевірку форми",
completed: false,
},
];
function TaskList({ items }) {
if (items.length === 0) {
return <p>Список завдань порожній.</p>;
}
return (
<ul>
{items.map((task) => (
<li
key={task.id}
className={task.completed ? "task task--completed" : "task"}
>
<span>{task.title}</span>
{task.completed && <span>Виконано</span>}
</li>
))}
</ul>
);
}
function Dashboard({ isLoading, error, items }) {
if (isLoading) {
return <p>Завантаження завдань...</p>;
}
if (error) {
return <p role="alert">Не вдалося завантажити дані: {error}</p>;
}
return (
<main>
<h1>Мої завдання</h1>
<p>
Усього завдань: <strong>{items.length}</strong>
</p>
<TaskList items={items} />
</main>
);
}
function App() {
return (
<Dashboard isLoading={false} error={null} items={tasks} />
);
}
createRoot(document.getElementById("root")).render(
<StrictMode>
<App />
</StrictMode>
);Для цього прикладу в HTML має бути контейнер:
<div id="root"></div>Вираз:
className={task.completed ? "task task--completed" : "task"}застосовує різний клас залежно від значення task.completed.
Вираз:
{task.completed && <span>Виконано</span>}додає напис лише для виконаних завдань.
Якщо умова або перетворення масиву складні, їх можна виконати до return. Це робить JSX коротшим:
function ActiveTasks({ tasks }) {
const activeTasks = tasks.filter((task) => !task.completed);
return (
<ul>
{activeTasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}Для невеликих списків такий підхід достатній. Важливо, щоб JSX описував структуру інтерфейсу, а не містив надмірну кількість складної логіки.
key// Погано: React не отримує ключ для кожного елемента
<ul>
{tasks.map((task) => (
<li>{task.title}</li>
))}
</ul>Потрібно додати стабільний ключ:
<ul>
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>// Погано: ключ змінюється під час кожного рендеру
{tasks.map((task) => (
<li key={Math.random()}>{task.title}</li>
))}Використовуйте ідентифікатор самого елемента:
{tasks.map((task) => (
<li key={task.id}>{task.title}</li>
))}&&// Якщо count дорівнює 0, у розмітці може з'явитися 0
{count && <p>Є нові елементи</p>}Явно перевіряйте число:
{count > 0 && <p>Є нові елементи</p>}if безпосередньо всередині JSXТакий код некоректний:
return (
<div>
{if (isVisible) {
<p>Текст</p>;
}}
</div>
);Для JSX використовуйте тернарний оператор або &&:
return (
<div>
{isVisible && <p>Текст</p>}
</div>
);Якщо map() повертає кілька сусідніх елементів, їх потрібно об’єднати фрагментом або контейнером:
function TaskDetails({ tasks }) {
return (
<ul>
{tasks.map((task) => (
<li key={task.id}>
<strong>{task.title}</strong>
<small>{task.completed ? "Готово" : "У роботі"}</small>
</li>
))}
</ul>
);
}У цьому прикладі кожна ітерація повертає один li, тому додатковий фрагмент не потрібен. Якщо ж потрібно повернути кілька сусідніх елементів, використовуйте фрагмент із ключем:
function TaskRows({ tasks }) {
return (
<>
{tasks.map((task) => (
<div key={task.id}>
<h2>{task.title}</h2>
<p>{task.completed ? "Виконано" : "Не виконано"}</p>
</div>
))}
</>
);
}Умовний рендеринг у JSX реалізується через if, тернарний оператор і &&.
return null дозволяє не створювати розмітку компонента.
Масиви JSX-елементів зазвичай створюються методом map().
Кожен елемент списку має отримувати стабільний унікальний key.
Найкращим ключем є ідентифікатор елемента, наприклад item.id.
Індекс масиву можна використовувати лише для незмінних статичних списків.
Числові умови через && потрібно записувати явно, наприклад count > 0 && ....