Kaip ištaisyti klaidą „Cannot Read Properties of Undefined (Reading map)“ React
React klaida Cannot read properties of undefined (reading 'map') reiškia, kad JavaScript bandė įvertinti tokį kodą kaip items.map(...), kai items buvo undefined. Svarbiausia dalis nėra žodis map; tai yra reikšmė, esanti tiesiai prieš .map().
Dabartinė React dokumentacija (React 19.2 rašymo metu) vis dar naudoja JavaScript masyvo map() metodą kaip standartinį būdą paversti kolekcijas į JSX elementus. MDN apibrėžia Array.prototype.map() kaip metodą, kuris sukuria naują masyvą taikydamas atgalinio skambučio funkciją kiekvienam elementui. Jei tikėtina masyvo reikšmė nebuvo inicializuota, dar nebuvo įkelta arba gaunama iš netikėtos API struktūros, skambinimas gali nepavykti, kol React spės atvaizduoti sąrašą. Žiūrėkite React vadovą sąrašų atvaizdavimui ir MDN Array.prototype.map() nuorodą.
Iliustracinis scenarijus: produktų sąrašas, kuris sugriūna prieš API baigiant darbą
Tai yra hipotetinis pavyzdys aiškinimui, o ne realus testo rezultatas. Įsivaizduokite mažą parduotuvės komponentą, kuris gauna produktus po to, kai komponentas pirmą kartą pasirodo. Kūrėjas parašo:
Pirmojo atvaizdavimo metu products yra undefined, nes useState nebuvo perduota pradinė reikšmė. React useState dokumentacija teigia, kad būsenos reikšmė pirmojo atvaizdavimo metu atitinka pateiktą pradinę būseną. Užklausa vykdoma vėliau, todėl atvaizdavimas gali pasiekti products.map(...) prieš atvykstant atsakymui. React useState nuoroda.
Dirbtiniu intelektu sugeneruota iliustracija, rodanti JavaScript klaidą, nukreiptą į sąrašo atvaizdavimą, kuris kviečia .map() neapibrėžtai reikšmei.
1 žingsnis: Raskite tiksliai, kuri reikšmė yra neapibrėžta
Pradėkite nuo krūvio steko (stack trace) ir raskite eilutę, kurioje yra .map(). Iliustraciniame komponente tai yra products.map(...), todėl products yra pirmoji reikšmė, kurią reikia patikrinti. Didesniame komponente nepavykusi išraiška gali būti data.items.map(), props.users.map() arba response.results.map().
Naudokite naršyklės derintuvą arba laikiną žurnalą tiesiai prieš atvaizdavimo logiką:
Tai atskiria kelias klaidas, kurios UI atrodo panašiai. Jei reikšmė yra undefined, tirkite inicializavimą arba trūkstamą savybę. Jei ji yra null, jūsų įkėlimo/duomenų modelis gali sąmoningai naudoti null. Jei tai objektas, galite skaityti neteisingą API atsakymo lygį. Jei tai eilutė arba skaičius, aukštesnio lygio duomenų sutartis skiriasi nuo to, ko tikisi komponentas.
Nesikeiskite automatiškai kiekvienos nepavykusios išraiškos su opcionaliu grandininiu susiejimu (optional chaining) prieš supratus reikšmę. Tai gali slopinti avariją, paliekant neteisingą duomenų srautą.
2 žingsnis: Inicializuokite kolekcijos būseną kaip kolekciją, jei tai atitinka jūsų modelį
Iliustraciniam produktų sąrašui tuščias masyvas yra protinga pradinė būsena, nes „dar nėra įkeltų produktų“ galima saugiai atvaizduoti kaip kolekciją su nuliu elementų:
const [products, setProducts] = useState([]);
Dabar pirmasis atvaizdavimas gali paleisti products.map(...), nes tuščias masyvas turi map metodą. Kai užklausa vėliau atnaujina būseną, React vėl atvaizduoja su grąžintais produktais.
Dirbtiniu intelektu sugeneruota iliustracija, rodanti sąrašo būsenos inicializavimą su useState([]), kad pirmasis atvaizdavimas turėtų masyvą.
Tai stiprus sprendimas, kai būsena konceptualiai visada yra masyvas. Tai mažiau tinkama, kai undefined arba null turi reikšmingą informaciją, pvz., „dar neužklausta“, o [] reiškia „užklausa baigta ir rezultatų nėra“. Tokiu atveju laikykite būsenas atskiras ir aiškiai atvaizduokite įkėlimo, klaidos ir tuščios būsenos sąlygas.
3 žingsnis: Patikrinkite API atsakymo struktūrą prieš dedant ją į būseną
Inicializavimas išsprendžia tik pirmąjį atvaizdavimą. Jis neapsaugos komponento, jei serveris galiausiai grąžins kitokią struktūrą. Tarkime, API iš tikrųjų grąžina:
Tada setProducts(data) saugo objektą, o ne masyvą. Tikėtina klaida pasikeičia į tokią kaip products.map is not a function. Teisingas priskyrimas būtų setProducts(data.products), jei tik ta savybė yra garantuotai masyvas.
Duomenims, kertantiems išorinę ribą, juos patikrinkite:
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);
});
Dirbtiniu intelektu sugeneruota iliustracija, rodanti sąrašo atvaizdavimo apsaugą naudojant Array.isArray() prieš naudojant .map().
Tai svarbu, nes „saugi numatytoji reikšmė“ neturėtų paversti netinkamų gamybos duomenų tyliai tuščiu puslapiu. Jei sutartis reikalauja masyvo, klaidos žurnalizavimas arba rodymas gali būti naudingesnis nei kiekvieno netikėto atsakymo konvertavimas į [].
4 žingsnis: Pasirinkite tinkamą atvaizdavimo apsaugą duomenims, kurių gali teisėtai trūkti
React palaiko įprastą JavaScript sąlyginį atvaizdavimą. Oficialioje dokumentacijoje parodyta, kaip naudoti if, ternary išraiškas ir && nuspręsti, kokį JSX grąžinti. React sąlyginio atvaizdavimo vadovas.
Iliustraciniam produktų puslapiui aiškus įkėlimo ir klaidų srautas dažnai yra geriausias:
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>
);
Opcionalus grandininis susiejimas taip pat yra tinkamas, kai „laikinai trūksta“ turėtų tiesiog sugeneruoti jokio susieto rezultato:
MDN paaiškina, kad opcionalus grandininis susiejimas (?.) nutraukia vykdymą, kai kairėje esanti reikšmė yra null arba undefined, vietoj to, kad mestų TypeError. MDN opcionalaus grandininio susiejimo nuoroda.
Dirbtiniu intelektu sugeneruota iliustracija, rodanti opcionalaus grandininio susiejimo naudojimą su users?.map(...) duomenims, kurie laikinai gali būti neapibrėžti.
Apribojimas yra svarbus: products?.map(...) užkerta kelią šiai konkrečiai nullish-access klaidai, bet neįrodo, kad products yra masyvas. Jei products tampa objektu, products?.map vis tiek išsprendžiamas į neapibrėžtą savybę, ir bandymas ją iškviesti gali nepavykti. Naudokite schemos validaciją arba Array.isArray(), kai duomenų tipas pats yra neaiškus.
Kurį sprendimą turėtumėte pasirinkti?
Situacija
Geriausias pirmasis žingsnis
Kodėl
Būsena visada turėtų būti sąrašas
useState([])
Pirmajam atvaizdavimui suteikia teisingą duomenų tipą.
Trūkstami duomenys turi reikšmingą įkėlimo būseną
Sąlyginis atvaizdavimas
Laiko „neįkelta“ atskirai nuo „įkelta, bet tuščia“.
API struktūra gali skirtis arba būti netinkama
Patikrinkite su Array.isArray() arba schema
Neleidžia blogiems išoriniams duomenims nepastebimai patekti į komponento būseną.
Prop yra neprivalomas pagal dizainą
Opcionalus grandininis susiejimas arba numatytoji prop reikšmė
Vengia nuorodos į teisėtai trūkstamą reikšmę.
Klaida atsiranda po refaktoringo
Patikrinkite savybių ir prop pavadinimus
Pervardytas laukas gali paversti anksčiau galiojusį masyvą neapibrėžtu.
Dažni atvejai, kurie atrodo kaip ta pati klaida
API grąžina { items: [...] }, bet komponentas tikisi masyvo
Patikrinkite tinklo atsakymą ir priskirkite masyvo savybę, o ne apvalkalo objektą. Nespekuliuokite struktūros iš seno pavyzdžio ar TypeScript sąsajos, jei gyvas atsakymas skiriasi.
Naudojant response.data.items.map(...), bet kokia trūkstama tarpinė savybė gali sukelti klaidą. Opcionalus grandininis susiejimas, toks kaip response?.data?.items, yra naudingas neaiškiems giliau esantiems reikšmėms skaityti, bet galutinę kolekciją patikrinkite prieš laikydami ją masyvu.
Pirmasis atvaizdavimas įvyksta prieš baigiant Effect užklausą
React Effects veikia po atvaizdavimo. React dokumentacija taip pat pažymi, kad rankinis duomenų gavimas Effect viduje yra dažnas kliento pusės programose, bet gali turėti trūkumų, tokių kaip „kriokliai“, trūkstami serverio atvaizduoti duomenys ir rankinis talpyklos/konkurencijos sąlygų valdymas. Jei naudojate React karkasą, jo integruotas duomenų įkėlimo mechanizmas gali būti geresnis architektūrinis pasirinkimas. React useEffect nuoroda.
Saugesnė iliustracinio komponento galutinė versija
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>
);
}
Ši versija sąmoningai skiria „neįkelta“, „nepavyko“, „tuščia“ ir „yra duomenų“. Tai yra išsamesnis kodas nei viena opcionalaus grandininio susiejimo išraiška, bet tai suteikia vartotojui prasmingą UI kiekvienai būsenai ir padaro netinkamą API atsakymą matomą derinimo metu.
Greitas patikrinimo sąrašas prieš laikant klaidą ištaisytą
Nustatykite tikslią reikšmę tiesiai prieš .map().
Patvirtinkite jos reikšmę ir tipą atvaizdavime, kuris nepavyksta.
Inicializuokite sąrašo būseną su [], kai tuščias masyvas tiksliai atitinka pradinę būseną.
Patikrinkite, ar API grąžina masyvą tiesiogiai, ar apgaubia jį objekto savybėje.
Patikrinkite išorinius duomenis prieš saugodami juos būsenoje, kai sutartis yra svarbi.
Naudokite įkėlimo būseną, jei undefined reiškia „dar neįkelta“.
Naudokite opcionalaus grandininio susiejimo, kai nebuvimas yra teisėtas, o ne kaip supratimo apie blogus duomenis pakaitalą.
Po refaktoringo patikrinkite pervardytus arba praleistus props.
Suteikite atvaizduotiems sąrašo elementams stabilias raktus iš duomenų, kaip rekomenduoja React sąrašų atvaizdavimo dokumentacija.
Iliustraciniam produktų sąrašui pagrindinė priežastis buvo tai, kad pirmasis atvaizdavimas gavo undefined, kai komponentas iškart tikėjosi masyvo. Realaus gyvenimo programoje ta pati klaidos žinutė gali kilti iš būsenos inicializavimo, props, giliau esančių savybių arba API duomenų. Pirmiausia ištaisykite duomenų sutartį, tada pasirinkite atvaizdavimo apsaugą, kuri tiksliai atitinka, ką „trūksta“ reiškia jūsų UI.