Головна
» Базові знання
»
Як виправити помилку “Cannot Read Properties of Undefined (Reading map)” у React
Як виправити помилку “Cannot Read Properties of Undefined (Reading map)” у React
Помилка React Cannot read properties of undefined (reading 'map') означає, що JavaScript спробував виконати вираз на кшталт items.map(...), коли items дорівнювало undefined. Важливим є не слово map, а значення, що стоїть безпосередньо перед .map().
У поточній документації React (React 19.2 на момент написання) метод map() з масивів JavaScript досі використовується як стандартний спосіб перетворення колекцій на елементи JSX. MDN визначає Array.prototype.map() як метод, який створює новий масив, застосовуючи колбек до кожного елемента. Якщо значення, яке ви очікуєте отримати як масив, не ініціалізоване, ще не завантажене або надходить з неочікуваної структури API, виклик може завершитися помилкою до того, як React зможе відобразити список. Дивіться посібник React з рендерингу списків та довідку MDN щодо Array.prototype.map().
Ілюстративний сценарій: список товарів, який падає до завершення запиту до API
Це гіпотетичний приклад для пояснення, а не реальний результат тестування. Уявіть невеликий компонент інтернет-магазину, який отримує товари після першого відображення компонента. Розробник пише:
Під час першого рендерингу products дорівнює undefined, оскільки початкове значення не було передано до useState. У документації React щодо useState зазначено, що значення стану під час першого рендерингу відповідає наданому початковому стану. Запит виконується пізніше, тому рендеринг може досягти рядка products.map(...) до отримання відповіді. Довідка React щодо useState.
Ілюстрація, згенерована ШІ, що демонструє помилку JavaScript, яка вказує на рендеринг списку, де викликається .map() для значення undefined.
Крок 1: Знайдіть точно, яке значення є undefined
Почніть зі стека викликів і знайдіть рядок, що містить .map(). У нашому ілюстративному компоненті це рядок products.map(...), тому products — це перше значення для перевірки. У більшому компоненті помилковим виразом може бути data.items.map(), props.users.map() або response.results.map().
Використовуйте відлагоджувач браузера або тимчасовий лог безпосередньо перед логікою рендерингу:
Це дозволяє розрізнити кілька помилок, які виглядають однаково в інтерфейсі. Якщо значення undefined, перевірте ініціалізацію або відсутню властивість. Якщо воно null, ваша модель даних або стан завантаження може явно використовувати null. Якщо це об'єкт, можливо, ви звертаєтеся до неправильного рівня вкладеності у відповіді API. Якщо це рядок або число, контракт даних від джерела відрізняється від того, що очікує компонент.
Не замінюйте автоматично кожен помилковий вираз на опціональний ланцюжок (optional chaining), перш ніж зрозуміти природу значення. Це може приховати падіння, залишивши некоректний потік даних.
Крок 2: Ініціалізуйте стан колекції як колекцію, якщо це відповідає вашій моделі
Для гіпотетичного списку товарів порожній масив є розумним початковим станом, оскільки “товари ще не завантажені” можна безпечно представити як колекцію з нульовою кількістю елементів:
const [products, setProducts] = useState([]);
Тепер перший рендеринг може виконати products.map(...), оскільки порожній масив має метод map. Коли запит пізніше оновить стан, React знову відобразить компонент із повернутими товарами.
Ілюстрація, згенерована ШІ, що демонструє ініціалізацію стану списку за допомогою useState([]), щоб перший рендеринг мав масив.
Це надійне рішення, коли стан концептуально завжди є масивом. Воно менш доречне, коли undefined або null несуть змістовну інформацію, наприклад “запит ще не виконано”, тоді як [] означає “запит завершено, результатів немає”. У такому випадку розрізняйте ці стани та явно відображайте стани завантаження, помилки та порожнього результату.
Крок 3: Перевіряйте структуру відповіді API перед записом у стан
Ініціалізація виправляє лише перший рендеринг. Вона не захистить компонент, якщо сервер згодом поверне іншу структуру. Припустімо, що API насправді повертає:
Тоді setProducts(data) збереже об'єкт, а не масив. Ймовірна помилка зміниться на щось на кшталт products.map is not a function. Правильним присвоєнням буде setProducts(data.products), за умови, що ця властивість гарантовано є масивом.
Для даних, що надходять із зовнішнього джерела, валідуйте їх:
fetch('/api/products')
.then(response => response.json())
.then(data => {
if (!Array.isArray(data.products)) {
throw new Error('Expected data.products to be an array');
}
setProducts(data.products);
})
.catch(error => {
console.error(error);
setError(error);
});
Ілюстрація, згенерована ШІ, що демонструє захист рендерингу списку за допомогою Array.isArray() перед використанням .map().
Це важливо, тому що “безпечне значення за замовчуванням” не повинно перетворювати некоректні виробничі дані на тихо порожню сторінку. Якщо масив вимагається контрактом, логування або відображення помилки може бути кориснішим, ніж перетворення кожної неочікуваної відповіді на [].
Крок 4: Оберіть правильний захист рендерингу для даних, які можуть легітимно відсутні
React підтримує звичайний умовний рендеринг JavaScript. Офіційна документація показує використання if, тернарних виразів та && для визначення того, який JSX повертати. Посібник React з умовного рендерингу.
Для гіпотетичної сторінки товарів явний потік завантаження та помилок часто є найзрозумілішим:
if (error) {
return <p>Could not load products.</p>;
}
if (products === undefined) {
return <p>Loading products…</p>;
}
if (products.length === 0) {
return <p>No products found.</p>;
}
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
Опціональний ланцюжок також є валідним, коли “тимчасова відсутність” повинна просто не створювати результату мапінгу:
MDN пояснює, що опціональний ланцюжок (?.) припиняє виконання, якщо значення ліворуч від нього є null або undefined, замість того щоб генерувати TypeError. Довідка MDN щодо опціонального ланцюжка.
Ілюстрація, згенерована ШІ, що демонструє опціональний ланцюжок із users?.map(...) для даних, які тимчасово можуть бути undefined.
Обмеження важливе: products?.map(...) запобігає цій конкретній помилці доступу до nullish-значення, але не доводить, що products є масивом. Якщо products стане об'єктом, products?.map все одно розв’яжеться у невизначену властивість, і спроба викликати її може завершитися помилкою. Використовуйте валідацію схеми або Array.isArray(), коли тип даних сам по собі є невизначеним.
Яке виправлення обрати?
Ситуація
Найкращий перший крок
Чому
Стан завжди повинен бути списком
useState([])
Надає першому рендерингу правильний тип даних.
Відсутні дані мають змістовний стан завантаження
Умовний рендеринг
Розрізняє “не завантажено” та “завантажено, але порожньо”.
Структура API може змінюватися або бути некоректною
Валідація за допомогою Array.isArray() або схеми
Запобігає потраплянню поганих зовнішніх даних у стан компонента непомітно.
Проп є опціональним за задумом
Опціональний ланцюжок або значення пропа за замовчуванням
Уникає розіменування легітимно відсутнього значення.
Помилка з’являється після рефакторингу
Перевірте назви властивостей та пропів
Перейменоване поле може перетворити раніше валідний масив на undefined.
Поширені випадки, які виглядають як одна й та сама помилка
API повертає { items: [...] }, але компонент очікує масив
Перевірте відповідь у вкладці Network і присвоюйте властивість-масив, а не обгортку-об'єкт. Не вгадуйте структуру за старим прикладом або інтерфейсом TypeScript, якщо жива відповідь відрізняється.
У виразі response.data.items.map(...) будь-яка відсутня проміжна властивість може спричинити помилку. Опціональний ланцюжок, такий як response?.data?.items, корисний для читання невизначених вкладених значень, але валідуйте кінцеву колекцію, перш ніж обробляти її як масив.
Перший рендеринг відбувається до завершення Effect із завантаженням даних
Effect у React виконуються після рендерингу. У документації React також зазначено, що ручне завантаження даних всередині Effect є поширеним у клієнтських додатках, але може мати недоліки, такі як каскади запитів, відсутність даних серверного рендерингу та необхідність ручного кешування/обробки умов гонки. Якщо ви використовуєте фреймворк на базі React, його вбудований механізм завантаження даних може бути кращим архітектурним рішенням. Довідка React щодо useEffect.
Безпечніша фінальна версія ілюстративного компонента
import { useEffect, useState } from 'react';
export default function ProductList() {
const [products, setProducts] = useState(undefined);
const [error, setError] = useState(null);
useEffect(() => {
let ignore = false;
fetch('/api/products')
.then(response => {
if (!response.ok) throw new Error('Request failed');
return response.json();
})
.then(data => {
if (!Array.isArray(data.products)) {
throw new Error('Expected products array');
}
if (!ignore) setProducts(data.products);
})
.catch(error => {
if (!ignore) setError(error);
});
return () => {
ignore = true;
};
}, []);
if (error) return <p>Could not load products.</p>;
if (products === undefined) return <p>Loading products…</p>;
if (products.length === 0) return <p>No products found.</p>;
return (
<ul>
{products.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Ця версія навмисно розрізняє стани “не завантажено”, “помилка”, “порожньо” та “є дані”. Це більш громіздко, ніж один вираз з опціональним ланцюжком, але надає користувачеві змістовний інтерфейс для кожного стану та робить некоректну відповідь API видимою під час відлагодження.
Швидкий чек-лист перед тим, як вважати помилку виправленою
Ідентифікуйте точне значення безпосередньо перед .map().
Підтвердьте його значення та тип під час рендерингу, який завершується помилкою.
Ініціалізуйте стан списку за допомогою [], якщо порожній масив точно відображає початковий стан.
Перевірте, чи API повертає масив безпосередньо, чи загортає його у властивість об'єкта.
Валідуйте зовнішні дані перед збереженням у стані, якщо контракт важливий.
Використовуйте стан завантаження, якщо undefined означає “ще не завантажено”.
Використовуйте опціональний ланцюжок, коли відсутність є легітимною, а не як заміну розумінню поганих даних.
Перевіряйте перейменовані або відсутні пропи після рефакторингу.
Надавайте відображеним елементам списку стабільні ключі з даних, як рекомендовано в документації React щодо рендерингу списків.
Для гіпотетичного списку товарів кореневою причиною було те, що перший рендеринг отримав undefined там, де компонент одразу очікував масив. У реальному додатку таке саме повідомлення про помилку може виникати через ініціалізацію стану, пропи, вкладені властивості або дані API. Спочатку виправте контракт даних, а потім оберіть захист рендерингу, який точно відображає, що означає “відсутність” у вашому інтерфейсі.