Αρχική
» ΒΑΣΙΚΕΣ ΓΝΩΣΕΙΣ
»
Πώς να διορθώσετε το σφάλμα “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. Η τεκμηρίωση του useState του React αναφέρει ότι η τιμή κατάστασης στην πρώτη απόδοση ταιριάζει με την αρχική κατάσταση που παρέχετε. Το fetch εκτελείται στη συνέχεια, οπότε η απόδοση μπορεί να φτάσει στο products.map(...) πριν φτάσει η απάντηση. Αναφορά React useState.
Εικόνα που δημιουργήθηκε από AI δείχνοντας το σφάλμα JavaScript που δείχνει σε μια απόδοση λίστας που καλεί .map() σε μια τιμή undefined.
Βήμα 1: Βρείτε ακριβώς ποια τιμή είναι undefined
Ξεκινήστε από το stack trace και εντοπίστε τη γραμμή που περιέχει το .map(). Στο παραδειγματικό εξάρτημα, αυτή η γραμμή είναι products.map(...), οπότε το products είναι η πρώτη τιμή προς επιθεώρηση. Σε ένα μεγαλύτερο εξάρτημα, η αποτυχημένη έκφραση μπορεί αντίθετα να είναι data.items.map(), props.users.map() ή response.results.map().
Χρησιμοποιήστε τον debugger του browser ή μια προσωρινή καταγραφή αμέσως πριν από τη λογική απόδοσης:
Αυτό διαχωρίζει διάφορα bugs που φαίνονται παρόμοια στο UI. Εάν η τιμή είναι undefined, ερευνήστε την αρχικοποίηση ή μια λείπουσα ιδιότητα. Εάν είναι null, το μοντέλο φόρτωσης/δεδομένων σας μπορεί να χρησιμοποιεί ρητά το null. Εάν είναι αντικείμενο, μπορεί να διαβάζετε το λάθος επίπεδο μιας απάντησης API. Εάν είναι συμβολοσειρά ή αριθμός, η σύμβαση δεδομένων της πηγής διαφέρει από αυτό που περιμένει το εξάρτημα.
Μην αντικαθιστάτε αυτόματα κάθε αποτυχημένη έκφραση με προαιρετική αλυσίδα (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 εξηγεί ότι η προαιρετική αλυσίδα (?.) διακόπτει τη ροή όταν η τιμή στα αριστερά της είναι null ή undefined αντί να προκαλέσει ένα TypeError. Αναφορά προαιρετικής αλυσίδας του MDN.
Εικόνα που δημιουργήθηκε από AI δείχνοντας προαιρετική αλυσίδα με users?.map(...) για δεδομένα που μπορεί προσωρινά να είναι undefined.
Ο περιορισμός είναι σημαντικός: το products?.map(...) αποτρέπει αυτό το συγκεκριμένο σφάλμα πρόσβασης σε nullish τιμή, αλλά δεν αποδεικνύει ότι το products είναι πίνακας. Εάν το products γίνει αντικείμενο, το products?.map εξακολουθεί να επιλύεται σε μια undefined ιδιότητα και η προσπάθεια κλήσης του μπορεί να αποτύχει. Χρησιμοποιήστε επαλήθευση σχήματος ή Array.isArray() όταν ο τύπος δεδομένων είναι αβέβαιος.
Ποια διόρθωση πρέπει να επιλέξετε;
Κατάσταση
Καλύτερη πρώτη κίνηση
Γιατί
Η κατάσταση πρέπει πάντα να είναι λίστα
useState([])
Δίνει στην πρώτη απόδοση τον σωστό τύπο δεδομένων.
Τα λείποντα δεδομένα έχουν μια σημαντική κατάσταση φόρτωσης
Δεσμευτική απόδοση
Κρατά το “δεν έχει φορτωθεί” ξεχωριστό από το “φορτώθηκε αλλά είναι κενό”.
Η δομή του API μπορεί να ποικίλλει ή να είναι κακοσχηματισμένη
Επαλήθευση με Array.isArray() ή σχήμα
Αποτρέπει την είσοδο κακών εξωτερικών δεδομένων στην κατάσταση του εξαρτήματος απαρατήρητα.
Η ιδιότητα (prop) είναι προαιρετική από σχεδιασμό
Προαιρετική αλυσίδα ή προεπιλεγμένη τιμή prop
Αποφεύγει την αναφορά σε μια δικαιολογημένα απουσιάζουσα τιμή.
Το σφάλμα εμφανίζεται μετά από ένα refactor
Ελέγξτε τα ονόματα ιδιοτήτων και props
Ένα μετονομασμένο πεδίο μπορεί να κάνει έναν πρώην έγκυρο πίνακα να γίνει undefined.
Συχνές περιπτώσεις που μοιάζουν με το ίδιο σφάλμα
Το API επιστρέφει { items: [...] }, αλλά το εξάρτημα περιμένει έναν πίνακα
Επιθεωρήστε την απάντηση Network και αναθέστε την ιδιότητα πίνακα αντί για το αντικείμενο περιέκτη. Μην μαντεύετε τη δομή από ένα παλιό παράδειγμα ή μια διεπαφή TypeScript εάν η ζωντανή απάντηση διαφέρει.
Με το response.data.items.map(...), οποιαδήποτε λείπουσα ενδιάμεση ιδιότητα μπορεί να προκαλέσει αποτυχία. Η προαιρετική αλυσίδα όπως response?.data?.items είναι χρήσιμη για την ανάδοση αβέβαιων ένθετων τιμών, αλλά επαληθεύστε την τελική συλλογή πριν τη χειριστείτε ως πίνακα.
Η πρώτη απόδοση συμβαίνει πριν ολοκληρωθεί ένα Effect fetch
Τα Effects του React εκτελούνται μετά την απόδοση. Η τεκμηρίωση του React σημειώνει επίσης ότι η χειροκίνητη ανάκτηση δεδομένων μέσα σε Effects είναι συνηθισμένη σε εφαρμογές client-side αλλά μπορεί να έχει μειονεκτήματα όπως waterfalls, λείποντα δεδομένα server-rendered και χειροκίνητη διαχείριση caching/race-condition. Εάν χρησιμοποιείτε ένα framework 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>
);
}
Αυτή η έκδοση διακρίνει σκόπιμα το “δεν έχει φορτωθεί”, “απέτυχε”, “κενό” και “έχει δεδομένα”. Είναι πιο εκτενές από μια ενιαία έκφραση προαιρετικής αλυσίδας, αλλά δίνει στον χρήστη ένα σημαντικό UI για κάθε κατάσταση και κάνει μια κακοσχηματισμένη απάντηση API ορατή κατά την αποσφαλμάτωση.
Γρήγορη λίστα ελέγχου πριν θεωρήσετε το bug διορθωμένο
Εντοπίστε την ακριβή τιμή αμέσως πριν από το .map().
Επιβεβαιώστε την τιμή και τον τύπο της στην απόδοση που αποτυγχάνει.
Αρχικοποιήστε την κατάσταση λίστας με [] όταν ένας κενός πίνακας αναπαριστά ακριβώς την αρχική κατάσταση.
Ελέγξτε εάν το API επιστρέφει έναν πίνακα απευθείας ή τον τυλίγει σε μια ιδιότητα αντικειμένου.
Επαληθεύστε τα εξωτερικά δεδομένα πριν τα αποθηκεύσετε στην κατάσταση όταν η σύμβαση έχει σημασία.
Χρησιμοποιήστε μια κατάσταση φόρτωσης εάν το undefined σημαίνει “δεν έχει φορτωθεί ακόμα”.
Χρησιμοποιήστε προαιρετική αλυσίδα όταν η απουσία είναι δικαιολογημένη, όχι ως υποκατάστατο για την κατανόηση κακών δεδομένων.
Ελέγξτε μετονομασμένα ή παραλειπόμενα props μετά από refactors.
Δώστε στα αποδοθέντα στοιχεία λίστας σταθερά keys από τα δεδομένα, όπως συνιστάται από την τεκμηρίωση απόδοσης λιστών του React.
Για την υποθετική λίστα προϊόντων, η αιτία ρίζας ήταν η πρώτη απόδοση να λαμβάνει undefined όπου το εξάρτημα περίμενε αμέσως έναν πίνακα. Σε μια πραγματική εφαρμογή, το ίδιο μήνυμα σφάλματος μπορεί να προέρχεται από την αρχικοποίηση κατάστασης, τα props, τις ένθετες ιδιότητες ή τα δεδομένα του API. Διορθώστε πρώτα τη σύμβαση δεδομένων και στη συνέχεια επιλέξτε έναν μηχανισμό απόδοσης που αναπαριστά ακριβώς τι σημαίνει “λείπει” στο UI σας.