Як виправити помилку “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

Це гіпотетичний приклад для пояснення, а не реальний результат тестування. Уявіть невеликий компонент інтернет-магазину, який отримує товари після першого відображення компонента. Розробник пише:

function ProductList() {
  const [products, setProducts] = useState();

  useEffect(() => {
    fetch('/api/products')
      .then(response => response.json())
      .then(data => setProducts(data));
  }, []);

  return (
    <ul>
      {products.map(product => (
        <li key={product.id}>{product.name}</li>
      ))}
    </ul>
  );
}

Під час першого рендерингу products дорівнює undefined, оскільки початкове значення не було передано до useState. У документації React щодо useState зазначено, що значення стану під час першого рендерингу відповідає наданому початковому стану. Запит виконується пізніше, тому рендеринг може досягти рядка products.map(...) до отримання відповіді. Довідка React щодо useState.

Ілюстрація, згенерована ШІ, що показує консоль браузера з помилкою Cannot read properties of undefined reading map у компоненті React.
Ілюстрація, згенерована ШІ, що демонструє помилку JavaScript, яка вказує на рендеринг списку, де викликається .map() для значення undefined.

Крок 1: Знайдіть точно, яке значення є undefined

Почніть зі стека викликів і знайдіть рядок, що містить .map(). У нашому ілюстративному компоненті це рядок products.map(...), тому products — це перше значення для перевірки. У більшому компоненті помилковим виразом може бути data.items.map(), props.users.map() або response.results.map().

Використовуйте відлагоджувач браузера або тимчасовий лог безпосередньо перед логікою рендерингу:

console.log('products:', products);
console.log('is array:', Array.isArray(products));

Це дозволяє розрізнити кілька помилок, які виглядають однаково в інтерфейсі. Якщо значення undefined, перевірте ініціалізацію або відсутню властивість. Якщо воно null, ваша модель даних або стан завантаження може явно використовувати null. Якщо це об'єкт, можливо, ви звертаєтеся до неправильного рівня вкладеності у відповіді API. Якщо це рядок або число, контракт даних від джерела відрізняється від того, що очікує компонент.

Не замінюйте автоматично кожен помилковий вираз на опціональний ланцюжок (optional chaining), перш ніж зрозуміти природу значення. Це може приховати падіння, залишивши некоректний потік даних.

Крок 2: Ініціалізуйте стан колекції як колекцію, якщо це відповідає вашій моделі

Для гіпотетичного списку товарів порожній масив є розумним початковим станом, оскільки “товари ще не завантажені” можна безпечно представити як колекцію з нульовою кількістю елементів:

const [products, setProducts] = useState([]);

Тепер перший рендеринг може виконати products.map(...), оскільки порожній масив має метод map. Коли запит пізніше оновить стан, React знову відобразить компонент із повернутими товарами.

Ілюстрація, згенерована ШІ, що показує оголошення useState у React, ініціалізоване порожнім масивом.
Ілюстрація, згенерована ШІ, що демонструє ініціалізацію стану списку за допомогою useState([]), щоб перший рендеринг мав масив.

Це надійне рішення, коли стан концептуально завжди є масивом. Воно менш доречне, коли undefined або null несуть змістовну інформацію, наприклад “запит ще не виконано”, тоді як [] означає “запит завершено, результатів немає”. У такому випадку розрізняйте ці стани та явно відображайте стани завантаження, помилки та порожнього результату.

Крок 3: Перевіряйте структуру відповіді API перед записом у стан

Ініціалізація виправляє лише перший рендеринг. Вона не захистить компонент, якщо сервер згодом поверне іншу структуру. Припустімо, що API насправді повертає:

{
  "products": [
    { "id": 1, "name": "Keyboard" },
    { "id": 2, "name": "Mouse" }
  ]
}

Тоді 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 у компоненті React перед викликом map.
Ілюстрація, згенерована ШІ, що демонструє захист рендерингу списку за допомогою 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>
);

Опціональний ланцюжок також є валідним, коли “тимчасова відсутність” повинна просто не створювати результату мапінгу:

{products?.map(product => (
  <ProductCard key={product.id} product={product} />
))}

MDN пояснює, що опціональний ланцюжок (?.) припиняє виконання, якщо значення ліворуч від нього є null або undefined, замість того щоб генерувати TypeError. Довідка MDN щодо опціонального ланцюжка.

Ілюстрація, згенерована ШІ, що показує використання опціонального ланцюжка перед map у компоненті React.
Ілюстрація, згенерована ШІ, що демонструє опціональний ланцюжок із users?.map(...) для даних, які тимчасово можуть бути undefined.

Обмеження важливе: products?.map(...) запобігає цій конкретній помилці доступу до nullish-значення, але не доводить, що products є масивом. Якщо products стане об'єктом, products?.map все одно розв’яжеться у невизначену властивість, і спроба викликати її може завершитися помилкою. Використовуйте валідацію схеми або Array.isArray(), коли тип даних сам по собі є невизначеним.

Яке виправлення обрати?

СитуаціяНайкращий перший крокЧому
Стан завжди повинен бути спискомuseState([])Надає першому рендерингу правильний тип даних.
Відсутні дані мають змістовний стан завантаженняУмовний рендерингРозрізняє “не завантажено” та “завантажено, але порожньо”.
Структура API може змінюватися або бути некоректноюВалідація за допомогою Array.isArray() або схемиЗапобігає потраплянню поганих зовнішніх даних у стан компонента непомітно.
Проп є опціональним за задумомОпціональний ланцюжок або значення пропа за замовчуваннямУникає розіменування легітимно відсутнього значення.
Помилка з’являється після рефакторингуПеревірте назви властивостей та пропівПерейменоване поле може перетворити раніше валідний масив на undefined.

Поширені випадки, які виглядають як одна й та сама помилка

API повертає { items: [...] }, але компонент очікує масив

Перевірте відповідь у вкладці Network і присвоюйте властивість-масив, а не обгортку-об'єкт. Не вгадуйте структуру за старим прикладом або інтерфейсом TypeScript, якщо жива відповідь відрізняється.

Батьківський компонент забуває передати проп

Це завершиться помилкою:

function UserList({ users }) {
  return users.map(user => <div key={user.id}>{user.name}</div>);
}

// Parent accidentally omits users:
<UserList />

Якщо відсутній проп повинен означати порожній список, значення за замовчуванням робить цей контракт явним:

function UserList({ users = [] }) {
  return users.map(user => <div key={user.id}>{user.name}</div>);
}

Відсутня вкладена властивість

У виразі 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. Спочатку виправте контракт даних, а потім оберіть захист рендерингу, який точно відображає, що означає “відсутність” у вашому інтерфейсі.

Залишити коментар

Як виправити помилку “Prisma Client Has Not Been Generated Yet”

Як виправити помилку “Prisma Client Has Not Been Generated Yet”

Виправте помилку незгенерованого Prisma Client, перевіривши генератор, схему, шлях виводу, імпорти, версії, налаштування монорепозиторію та кроки збірки під час розгортання.

Як виправити помилку SSL-сертифіката: не вдалося отримати локальний сертифікат емітента в Git

Як виправити помилку SSL-сертифіката: не вдалося отримати локальний сертифікат емітента в Git

Виправте помилку Git «не вдалося отримати локальний сертифікат емітента», визначивши механізм довіри, встановивши правильний ланцюжок ЦС та зберігаючи перевірку SSL увімкненою.

Як виправити помилку таймауту мережі MongoDB у з'єднанні Mongoose

Як виправити помилку таймауту мережі MongoDB у з'єднанні Mongoose

Виправте помилки таймауту мережі MongoDB у Mongoose, визначивши тип таймауту, перевіривши доступність Atlas або TCP, виправивши URI та налаштувавши таймаути лише за необхідності.

Як виправити помилку «Execution Policy Restricted» у Windows PowerShell

Як виправити помилку «Execution Policy Restricted» у Windows PowerShell

Виправте помилку обмеженої політики виконання PowerShell, перевіривши область дії та групову політику, а потім обравши RemoteSigned, Unblock-File або тимчасовий параметр сесії.

Як виправити помилку npm ERR! code ERESOLVE: конфлікт залежностей-партнерів

Як виправити помилку npm ERR! code ERESOLVE: конфлікт залежностей-партнерів

Виправте конфлікти залежностей-партнерів npm ERESOLVE, визначивши несумісний діапазон пакетів, узгодивши версії, використовуючи npm explain та npm ls, а також розглядаючи legacy-peer-deps або force лише як контрольовані резервні варіанти.

Як виправити помилку підключення Redis до 127.0.0.1:6379

Як виправити помилку підключення Redis до 127.0.0.1:6379

Виправте помилки відмови у підключенні Redis на 127.0.0.1:6379, перевіривши сервер, порт, мережу Docker, redis.conf, автентифікацію та TLS.

Як виправити внутрішню помилку 500 у серверних компонентах Next.js

Як виправити внутрішню помилку 500 у серверних компонентах Next.js

Виправте помилки 500 у серверних компонентах Next.js, аналізуючи логи сервера, перевіряючи запити даних та змінні середовища, обробляючи помилки та перевіряючи збірку для продакшену.

Як виправити помилку CrashLoopBackOff у Kubernetes у локальному Minikube

Як виправити помилку CrashLoopBackOff у Kubernetes у локальному Minikube

Діагностуйте та виправляйте помилку CrashLoopBackOff у Kubernetes у локальному Minikube, перевіряючи стан пода, попередні логи, причини завершення роботи, проби, конфігурацію, ліміти пам’яті та стан кластера.

Як виправити помилку «Docker Desktop Engine Stopped» у Windows 11

Як виправити помилку «Docker Desktop Engine Stopped» у Windows 11

Виправте помилку «Docker Desktop Engine Stopped» у Windows 11, перевіривши статус Docker, оновивши та перезавантаживши WSL 2, підтвердивши віртуалізацію та використавши діагностику перед скиданням налаштувань.

Як виправити помилку Uncaught ReferenceError: process is not defined у Vite

Як виправити помилку Uncaught ReferenceError: process is not defined у Vite

Виправте помилку 'process is not defined' у Vite, замінивши використання process.env у стилі Node.js, правильно налаштувавши змінні VITE_ та перевіривши залежності.