Начало
» Основни познания
»
Как да поправите грешката „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() като метод, който създава нов масив, като прилага обратна извикана функция (callback) към всеки елемент. Ако стойността, която очаквате да бъде масив, не е инициализирана, все още не е заредена или идва от неочаквана структура на API, извикването може да се провали, преди React да успее да рендира списъка. Вижте ръководството на React за рендиране на списъци и референцията на MDN за Array.prototype.map().
Илюстративен сценарий: списък с продукти, който крашва, преди API да приключи
Това е хипотетичен пример за обяснение, а не реален резултат от тест. Представете си малък компонент за витрина, който извлича продукти след първото появяване на компонента. Разработчикът пише:
При първото рендиране products е undefined, защото не е подадена начална стойност на useState. Документацията на React за useState посочва, че стойността на състоянието при първото рендиране съвпада с предоставеното начално състояние. Извличането (fetch) се изпълнява след това, така че рендирането може да достигне до products.map(...), преди отговорът да пристигне. Референция на React useState.
Илюстрация, генерирана от AI, на JavaScript грешката, сочеща към рендиране на списък, което извиква .map() върху undefined стойност.
Стъпка 1: Намерете точно коя стойност е undefined
Започнете от стековата следа (stack trace) иLocирайте реда, съдържащ .map(). В илюстративния компонент този ред е products.map(...), така че products е първата стойност за проверка. В по-голям компонент проваленият израз може да бъде вместо това data.items.map(), props.users.map() или response.results.map().
Използвайте дебъгера на браузъра или временен лог непосредствено преди логиката за рендиране:
Това разграничава няколко бъга, които изглеждат подобно в UI. Ако стойността е undefined, разследвайте инициализацията или липсващо свойство. Ако е null, вашият модел за зареждане/данни може изрично да използва null. Ако е обект, може да четете грешното ниво на отговор от API. Ако е низ или число, горният поток на данните (upstream data contract) е различен от очаквания от компонента.
Не заменяйте автоматично всеки провалещ се израз с опционално верижване (optional chaining), преди да разберете стойността. Това може да потисне краша, като остави некоректния поток на данни на място.
Стъпка 2: Инициализирайте състоянието на колекцията като колекция, когато това съответства на вашия модел
За хипотетичния списък с продукти, празният масив е разумно начално състояние, защото „няма заредени продукти все още“ може безопасно да бъде представено като колекция с нула елемента:
const [products, setProducts] = useState([]);
Сега първото рендиране може да изпълни products.map(...), защото празният масив има метод map. Когато заявката по-късно актуализира състоянието, React рендира отново с върнатите продукти.
Илюстрация, генерирана от AI, на инициализиране на състоянието на списъка с 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);
});
Илюстрация, генерирана от AI, на предпазител за рендиране на списък с 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 обяснява, че опционалното верижване (?.) прекъсва изпълнението (short-circuits), когато стойността отляво му е null или undefined, вместо да хвърли TypeError. Референция на MDN за опционално верижване.
Илюстрация, генерирана от AI, на опционално верижване с users?.map(...) за данни, които временно може да са undefined.
Ограничението е важно: products?.map(...) предотвратява тази конкретна грешка при достъп до nullish стойност, но не доказва, че products е масив. Ако products стане обект, products?.map все пак се разрешава до undefined свойство и опитът за извикването му може да се провали. Използвайте валидация на схемата или Array.isArray(), когато самият тип данни е несигурен.
Кое решение трябва да изберете?
Ситуация
Най-добър първи ход
Защо
Състоянието винаги трябва да е списък
useState([])
Давa на първото рендиране правилния тип данни.
Липсващите данни имат смислено състояние на зареждане
Условно рендиране
Пази „не е заредено“ отделно от „заредено, но празно“.
Формата на API може да варира или да е некоректна
Валидирайте с Array.isArray() или схема
Предотвратява лоши външни данни да влязат в състоянието на компонента незабелязано.
Пропът е опционален по дизайн
Опционално верижване или стойност на проп по подразбиране
Избягва разпакетирането (dereferencing) на легитимно отсъстваща стойност.
Грешката се появява след рефакторинг
Проверете имената на свойствата и проповете
Преименувано поле може да направи някога валиден масив да стане undefined.
Чести случаи, които изглеждат като една и съща грешка
API връща { items: [...] }, но компонентът очаква масив
Инспектирайте отговора в мрежовия таб (Network) и присвоете свойството с масива, а не обвиващия обект. Не отгатвайте формата от стар пример или TypeScript интерфейс, ако живият отговор се различава.
При response.data.items.map(...), всяко липсващо междинно свойство може да доведе до провал. Опционалното верижване като response?.data?.items е полезно за четене на несигурни вложени стойности, но валидирайте крайната колекция, преди да я третирате като масив.
Първото рендиране се случва, преди Effect за извличане да приключи
React Effects се изпълняват след рендирането. Документацията на React също отбелязва, че ръчното извличане на данни вътре в Effects е често срещано в клиентски приложения, но може да има недостатъци като водопади (waterfalls), липсващи данни от сървърно рендиране и ръчна работа по кеширането/състоянията на надпревара (race-conditions). Ако използвате React framework, неговият вграден механизъм за зареждане на данни може да е по-добър архитектурен избор. Референция на 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>
);
}
Тази версия намерено разграничава „не е заредено“, „неуспех“, „празно“ и „има данни“. Това е по-многословно от един израз с опционално верижване, но дава на потребителя смислен UI за всяко състояние и прави некоректния отговор от API видим по време на дебъгване.
Бърз чеклист, преди да считате бъга за поправен
Идентифицирайте точната стойност непосредствено преди .map().
Потвърдете нейната стойност и тип при рендирането, което се проваля.
Инициализирайте състоянието на списъка с [], когато празният масив точно представлява началното състояние.
Проверете дали API връща масив директно или го обвива в свойство на обект.
Валидирайте външните данни, преди да ги съхраните в състоянието, когато контрактът е важен.
Използвайте състояние на зареждане, ако undefined означава „не е заредено все още“.
Използвайте опционално верижване, когато отсъствието е легитимно, а не като заместител на разбирането на лошите данни.
Проверете преименувани или пропуснати пропове след рефакторинги.
Дайте на рендираните елементи от списъка стабилни ключове от данните, както се препоръчва от документацията на React за рендиране на списъци.
За хипотетичния списък с продукти коренната причина беше, че първото рендиране получава undefined, където компонентът веднага очаква масив. В реално приложение същото съобщение за грешка може да идва от инициализацията на състоянието, проповете, вложените свойства или данните от API. Поправете контракта за данните първо, след което изберете предпазител при рендиране, който точно представлява какво означава „липсва“ във вашия UI.