TypeError: Cannot read properties of undefined (reading 'name') est la ligne la plus courante d’une console de navigateur, et l’une des plus mal comprises. Presque tout le monde qui la voit cherche la chose appelée name. La chose appelée name se porte très bien. L’erreur concerne ce qui était censé la contenir.

C’est toute la difficulté de ce message en une phrase. Il nomme la propriété que vous essayiez de lire, et ne dit rien de la variable qui s’est révélée vide. Le correctif ne se trouve donc jamais là où pointe le message, et la première minute de chaque investigation consiste à comprendre ce dont le message parle réellement.

Lire le message

Prenez le code user.profile.name. Si user existe mais n’a pas de profile, alors user.profile vaut undefined, et lire .name sur undefined déclenche une exception. Le message dit (reading 'name'). Il ne dit pas que profile est manquant, et name est le seul mot de l’expression qui n’est pas le problème.

Les navigateurs formulent cela différemment, ce qui compte quand le même bug arrive de la part de trois personnes :

  • Chrome et Edge : Cannot read properties of undefined (reading 'name'). Les anciennes versions disaient Cannot read property 'name' of undefined, avec le même sens.
  • Firefox : TypeError: user.profile is undefined, ou can't access property "name", user.profile is undefined. Firefox nomme l’expression restée vide, ce qui est plus utile.
  • Safari : TypeError: undefined is not an object (evaluating 'user.profile.name'). Safari vous donne l’expression entière et vous laisse déterminer quelle partie a échoué.

Trois formulations, un seul bug. Une équipe qui ouvre ses tickets en collant la ligne de console en ouvrira trois, et celle de Firefox est la seule à dire où regarder.

Il existe une erreur cousine, Cannot read properties of null, qui n’est pas tout à fait la même. null est ce que vous obtenez d’une recherche qui s’est exécutée et n’a rien trouvé : document.querySelector('.total') sur une page sans élément .total. undefined est ce que vous obtenez de quelque chose qui n’a jamais été défini du tout : une propriété qui n’existe pas sur l’objet, une variable déclarée mais non assignée, une fonction qui n’a rien renvoyé. null pointe généralement vers la page ; undefined pointe généralement vers les données.

D’où vient l’undefined

Le message est le même dans tous les cas. La cause fait partie d’une courte liste, et le nom de la propriété entre parenthèses donne un bon indice sur laquelle.

Les données ne sont pas encore arrivées. La page s’est affichée avant que la requête censée la remplir ne soit revenue, et le code a lu response.items.length alors que response était encore la valeur de remplacement. C’est le cas qui apparaît sur les connexions lentes et pas sur les rapides, sur un téléphone et pas sur un ordinateur portable, pour le client et jamais pour le développeur. (reading 'length') et (reading 'map') sont les indices typiques : quelque chose attendait un tableau et n’a rien reçu.

La structure a changé. L’API renvoyait user.profile ; maintenant elle ne le renvoie que pour les comptes qui en ont renseigné un, ou elle a renommé la clé, ou le champ est remonté d’un niveau. Rien n’a changé côté client, et l’erreur a commencé le jour du déploiement de quelqu’un d’autre. C’est la version qui arrive sous la forme « ça marchait hier ».

La clé n’est pas orthographiée pareil. item.userId dans le code, item.user_id dans la réponse. Rien ne vous avertit : JavaScript lit une propriété qui n’existe pas comme undefined et continue, et l’erreur ne surgit qu’une étape plus tard, quand quelque chose essaie de lire à travers.

C’est une liste vide. results[0].title quand results vaut []. Du code parfaitement valide, jusqu’à la première recherche qui ne trouve rien. Les données de test sont rarement vides ; les données de production le sont souvent.

Quelque chose n’a rien renvoyé. Une fonction dont un chemin de code oublie de faire return, une fonction async dont l’appelant a oublié de faire await et a reçu une promesse au lieu d’une valeur, un .find() qui n’a trouvé aucune correspondance. Tout cela produit undefined et le transmet à la ligne suivante.

Le code est minifié. En production, le message affiche Cannot read properties of undefined (reading 'a'), parce que la propriété a été renommée par le build. Le numéro de ligne pointe vers une seule ligne énorme. Sans source map, le message ne dit plus rien du tout, et il ne reste que la pile des requêtes et des clics qui y ont mené.

Ce que ça donne de l’autre côté

Rien de tout cela n’est visible pour la personne qui est tombée dessus. Ce qu’elle voit, c’est une section de la page qui ne s’est jamais remplie, un bouton qui ne fait rien, un formulaire qui ne veut pas s’envoyer. Rien sur la page ne dit « une erreur s’est produite », parce que l’erreur s’est produite à l’intérieur d’un script et que le script s’est simplement arrêté. La ligne de console existe, dans un panneau qu’elle n’a jamais ouvert.

Le rapport dit donc « la page de commande est vide ». Et le développeur la reproduit, avec une connexion rapide, un compte complet et une liste non vide, et elle n’est pas vide.

Faible
La page de commande est vide pour un client, impossible à reproduire.
Mieux
Console : TypeError: Cannot read properties of undefined (reading 'items'), orders.js line 214. La requête envoyée à /api/orders juste avant a renvoyé 200 avec un corps vide. Le client n'a pas encore de commande.

La seconde version est un correctif prêt à l’emploi. Tout ce qu’elle contient vient de la page au moment de l’échec : la ligne de console, la requête précédente, et la réponse censée contenir les données. C’est la preuve dont cette erreur a besoin, et c’est exactement la preuve qu’une personne face à une page vide ne peut pas fournir elle-même. Notre guide pour rédiger un rapport de bug détaille ce qu’il faut demander ; en résumé, pour cette erreur, la console et le journal réseau sont le rapport, et la description n’est qu’une légende.

Session Replay

Extension Chrome gratuite. Un clic sur la page qui pose problème capture l'écran, la console et le journal réseau, et vous donne un lien à coller dans le ticket.

Obtenir l'extension

Trouver la cause dans les outils de développement

Quand vous avez la page sous les yeux et que l’erreur se reproduit, trois gestes suffisent à la régler.

Suspendre sur les exceptions. Dans le panneau Sources de Chrome, l’icône de pause avec « Pause on uncaught exceptions » arrête le script sur la ligne qui a déclenché l’exception, avec chaque variable encore dans sa portée. Survolez l’expression et vous voyez quelle partie de user.profile.name vaut undefined, ce que le message ne vous aurait jamais dit.

Regardez la requête précédente. Passez à l’onglet Network et trouvez la réponse censée remplir l’objet. Neuf fois sur dix, la réponse est là : un corps vide, un 200 avec un message d’erreur à l’intérieur, une clé au nom différent, ou une requête qui n’est jamais partie parce qu’elle attendait autre chose. L’article sur failed to fetch couvre le cas où la requête elle-même a échoué ; cet article couvre le cas où elle a réussi et a renvoyé la mauvaise structure.

Vérifiez l’ordre des événements. Si l’erreur n’apparaît que par moments, c’est presque toujours la première cause ci-dessus : une lecture qui s’exécute avant que les données ne soient là. Limitez le débit réseau dans l’onglet Network sur « Slow 3G » et rechargez. Si l’erreur apparaît de façon fiable, c’est une course critique, et le correctif consiste à attendre les données plutôt qu’à protéger la lecture.

Le correctif qui n’en est pas un

user?.profile?.name fait disparaître l’erreur. Il ne fait pas apparaître le profil. Le chaînage optionnel transforme un plantage en undefined silencieux, qui se propage ensuite dans la page sous forme de chaîne vide, de ligne manquante, de bouton sans libellé, et le bug devient invisible pour vous tout en restant visible pour le client.

C’est le bon outil quand l’absence est légitime : un compte qui n’a réellement pas encore de profil. C’est le mauvais outil quand l’absence est le bug. La question à se poser avant d’ajouter un ?. est de savoir si la valeur devrait jamais être manquante. Si ce n’est pas le cas, le plantage vous disait quelque chose, et une garde est une façon de ne pas écouter.

Ce qu’il faut en retenir

Le message nomme la propriété que vous demandiez et cache ce qui était vide. Lisez-le comme « l’étape précédente n’a rien produit », et allez regarder cette étape : la requête, la valeur de retour, le nom de la clé, la liste qui était vide. Le correctif se trouve toujours en amont du numéro de ligne.

Et quand le rapport vient de l’écran de quelqu’un d’autre, la ligne de console et le journal réseau ne sont pas du contexte pour le bug. Pour cette erreur, ils sont le bug.