Domů
» Základní znalosti
»
Jak opravit chybu „Cannot read properties of undefined (reading 'map')“ v Reactu
Jak opravit chybu „Cannot read properties of undefined (reading 'map')“ v Reactu
Chyba Reactu Cannot read properties of undefined (reading 'map') znamená, že se JavaScript pokusil vyhodnotit něco jako items.map(...), zatímco items bylo undefined. Důležitá není samotná funkce map, ale hodnota bezprostředně před .map().
Aktuální dokumentace Reactu (React 19.2 v době psaní tohoto textu) stále používá metodu JavaScriptu map() pro pole jako standardní způsob, jak převést kolekce na JSX elementy. MDN definuje Array.prototype.map() jako metodu, která vytváří nové pole aplikací callbacku na každý prvek. Pokud hodnota, kterou očekáváte jako pole, nebyla inicializována, ještě se nenačetla nebo pochází z neočekávané struktury odpovědi API, může volání selhat dříve, než React stihne vykreslit seznam. Podívejte se na průvodce vykreslováním seznamů v Reactu a na referenci MDN pro Array.prototype.map().
Ilustrativní scénář: seznam produktů, který spadne, než se dokončí načtení z API
Toto je hypotetický příklad pro vysvětlení, nikoliv výsledek reálného testu. Představte si malou komponentu obchodu, která načítá produkty poté, co se komponenta poprvé objeví. Vývojář napíše:
Při prvním vykreslení je productsundefined, protože funkci useState nebyla předána žádná počáteční hodnota. Dokumentace Reactu k useState uvádí, že hodnota stavu při prvním vykreslení odpovídá počátečnímu stavu, který poskytnete. Načítání dat probíhá až následně, takže vykreslování může narazit na products.map(...) dříve, než dorazí odpověď. Reference React useState.
Ilustrace vygenerovaná AI zobrazující chybu JavaScriptu ukazující na vykreslení seznamu, které volá .map() na nedefinované hodnotě.
Krok 1: Najděte přesně, která hodnota je nedefinovaná
Začněte u stack trace a najděte řádek obsahující .map(). V ilustrativní komponentě je tímto řádkem products.map(...), takže products je první hodnota, kterou je třeba prozkoumat. Ve větší komponentě může být selhávající výraz místo toho data.items.map(), props.users.map() nebo response.results.map().
Použijte debugger prohlížeče nebo dočasný log bezprostředně před logikou vykreslování:
Tím oddělíte několik chyb, které vypadají v UI podobně. Pokud je hodnota undefined, zkoumejte inicializaci nebo chybějící vlastnost. Pokud je null, váš model načítání/dat může explicitně používat null. Pokud je to objekt, možná čtete špatnou úroveň odpovědi API. Pokud je to řetězec nebo číslo, datová smlouva od zdroje se liší od toho, co komponenta očekává.
Neautomaticky nahrazujte každý selhávající výraz volitelným řetězením (optional chaining), dokud nepochopíte danou hodnotu. To může potlačit pád, ale ponechat nesprávný tok dat na místě.
Krok 2: Inicializujte stav kolekce jako kolekci, pokud to odpovídá vašemu modelu
Pro hypotetický seznam produktů je prázdné pole rozumným počátečním stavem, protože „žádné produkty ještě nenačteny“ lze bezpečně reprezentovat jako kolekci s nulovým počtem položek:
const [products, setProducts] = useState([]);
Nyní může první vykreslení spustit products.map(...), protože prázdné pole má metodu map. Když později požadavek aktualizuje stav, React znovu vykreslí s vrácenými produkty.
Ilustrace vygenerovaná AI zobrazující inicializaci stavu seznamu pomocí useState([]), aby první vykreslení mělo pole.
Toto je silné řešení, když je stav konceptuálně vždy pole. Je méně vhodné, když undefined nebo null nese smysluplnou informaci, například „ještě nevyžádáno“, zatímco [] znamená „požadavek dokončen a jsou nula výsledků“. V takovém případě udržujte stavy odlišné a explicitně vykreslujte podmínky načítání, chyby a prázdného stavu.
Krok 3: Ověřte tvar odpovědi API před uložením do stavu
Inicializace řeší pouze první vykreslení. Neochrání komponentu, pokud server nakonec vrátí jinou strukturu. Předpokládejme, že API ve skutečnosti vrací:
Pak setProducts(data) uloží objekt, nikoli pole. Pravděpodobná chyba se změní na něco jako products.map is not a function. Správné přiřazení by bylo setProducts(data.products), za předpokladu, že tato vlastnost je zaručeně pole.
Pro data přicházející z vnější hranice je validujte:
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);
});
Ilustrace vygenerovaná AI zobrazující ochranu vykreslení seznamu pomocí Array.isArray() před použitím .map().
To je důležité, protože „bezpečná výchozí hodnota“ by neměla proměnit špatná produkční data na tiše prázdnou stránku. Pokud je pole vyžadováno smlouvou, logování nebo zobrazení chyby může být užitečnější než převod každé neočekávané odpovědi na [].
Krok 4: Vyberte správnou ochranu při vykreslování pro data, která mohou oprávněně chybět
React podporuje běžné podmíněné vykreslování v JavaScriptu. Oficiální dokumentace ukazuje použití if, ternárních výrazů a && k rozhodnutí, jaký JSX vrátit. Průvodce podmíněným vykreslováním v Reactu.
Pro hypotetickou stránku produktů je explicitní tok načítání a chyby často nejjasnější:
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>
);
Volitelné řetězení je také platné, když „chybí zatím“ by mělo jednoduše vyprodukovat žádný namapovaný výsledek:
MDN vysvětluje, že volitelné řetězení (?.) přeruší vyhodnocování, pokud je hodnota vlevo od něj null nebo undefined, místo aby vyhodilo TypeError. Reference MDN pro volitelné řetězení.
Ilustrace vygenerovaná AI zobrazující volitelné řetězení s users?.map(...) pro data, která mohou být dočasně nedefinovaná.
Omezení je důležité: products?.map(...) zabraňuje této konkrétní chybě přístupu k nullish hodnotě, ale nedokazuje, že products je pole. Pokud se products stane objektem, products?.map stále vyhodnotí na nedefinovanou vlastnost a pokus o její volání může selhat. Použijte validaci schématu nebo Array.isArray(), pokud je datový typ sám o sobě nejistý.
Které řešení byste měli zvolit?
Situace
Nejlepší první krok
Proč
Stav by měl být vždy seznam
useState([])
Dá prvnímu vykreslení správný datový typ.
Chybějící data mají smysluplný stav načítání
Podmíněné vykreslování
Udržuje „nenačteno“ odděleně od „načteno, ale prázdné“.
Tvar API se může lišit nebo být chybný
Validace pomocí Array.isArray() nebo schématu
Zabraňuje vstupu špatných externích dat do stavu komponenty bez povšimnutí.
Prop je záměrně volitelný
Volitelné řetězení nebo výchozí hodnota propu
Vyhýbá se dereferencování oprávněně chybějící hodnoty.
Chyba se objeví po refaktoringu
Zkontrolujte názvy vlastností a propů
Přejmenované pole může způsobit, že dříve platné pole se stane nedefinovaným.
Běžné případy, které vypadají jako stejná chyba
API vrací { items: [...] }, ale komponenta očekává pole
Zkontrolujte odpověď v síti a přiřaďte vlastnost pole, nikoli obalující objekt. Nehádejte tvar ze starého příkladu nebo TypeScript rozhraní, pokud se živá odpověď liší.
Při response.data.items.map(...) může selhat jakákoli chybějící mezivlastnost. Volitelné řetězení jako response?.data?.items je užitečné pro čtení nejistých vnořených hodnot, ale před zpracováním jako pole ověřte konečnou kolekci.
První vykreslení proběhne před dokončením Effectu pro načtení dat
React Effects se spouštějí po vykreslení. Dokumentace Reactu také uvádí, že ruční načítání dat uvnitř Effectů je běžné v klientských aplikacích, ale může mít nevýhody, jako jsou vodopády požadavků, chybějící data vykreslená na serveru a ruční práce s cache a race conditions. Pokud používáte framework postavený na Reactu, jeho vestavěný mechanismus načítání dat může být lepší architektonickou volbou. Reference React useEffect.
Bezpečnější finální verze ilustrativní komponenty
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>
);
}
Tato verze záměrně rozlišuje „nenačteno“, „selhalo“, „prázdné“ a „má data“. Je to obsáhlejší než jeden výraz s volitelným řetězením, ale poskytuje uživateli smysluplné UI pro každý stav a činí chybnou odpověď API viditelnou během ladění.
Rychlý kontrolní seznam, než budete považovat chybu za opravenou
Identifikujte přesnou hodnotu bezprostředně před .map().
Potvrďte její hodnotu a typ při selhávajícím vykreslení.
Inicializujte stav seznamu pomocí [], pokud prázdné pole přesně reprezentuje počáteční stav.
Zkontrolujte, zda API vrací pole přímo, nebo ho obaluje ve vlastnosti objektu.
Validujte externí data před uložením do stavu, pokud na smlouvě záleží.
Použijte stav načítání, pokud undefined znamená „ještě nenačteno“.
Použijte volitelné řetězení, když je absence oprávněná, nikoli jako náhradu za pochopení špatných dat.
Po refaktoringu zkontrolujte přejmenované nebo vynechané propy.
Dodejte vykresleným položkám seznamu stabilní klíče z dat, jak doporučuje dokumentace Reactu pro vykreslování seznamů.
Pro hypotetický seznam produktů byla příčinou chyby skutečnost, že první vykreslení obdrželo undefined tam, kde komponenta okamžitě očekávala pole. V reálné aplikaci může stejná chybová zpráva pocházet z inicializace stavu, propů, vnořených vlastností nebo dat z API. Nejprve opravte datovou smlouvu, poté vyberte ochranu při vykreslování, která přesně reprezentuje, co „chybějící“ znamená ve vašem UI.