TypeError: Cannot read properties of undefined (reading 'name') é a linha mais comum no console do navegador, e uma das mais mal interpretadas. Quase todo mundo que a vê procura a coisa chamada name. A coisa chamada name está bem. O erro está em quem deveria estar carregando esse valor.

Essa é toda a dificuldade dessa mensagem numa frase só. Ela nomeia a propriedade que você tentava ler e não diz nada sobre a variável que acabou vazia. Por isso a correção nunca está onde a mensagem aponta, e o primeiro minuto de toda investigação é gasto descobrindo do que a mensagem realmente está falando.

Lendo a mensagem

Pegue o código user.profile.name. Se user existe mas não tem profile, então user.profile é undefined, e ler .name de undefined lança uma exceção. A mensagem diz (reading 'name'). Ela não diz que profile está faltando, e name é a única palavra na expressão que não é o problema.

Diferentes navegadores formulam isso de maneiras diferentes, o que importa quando o mesmo bug chega vindo de três pessoas diferentes:

  • Chrome e Edge: Cannot read properties of undefined (reading 'name'). Versões mais antigas diziam Cannot read property 'name' of undefined, com o mesmo significado.
  • Firefox: TypeError: user.profile is undefined, ou can't access property "name", user.profile is undefined. O Firefox nomeia a expressão que estava vazia, o que é mais útil.
  • Safari: TypeError: undefined is not an object (evaluating 'user.profile.name'). O Safari te dá a expressão inteira e deixa você descobrir qual parte falhou.

Três formulações, um bug só. Um time que registra bugs colando a linha do console vai abrir três tickets, e o do Firefox é o único que diz onde olhar.

Existe uma mensagem irmã, Cannot read properties of null, e ela não é bem o mesmo erro. null é o que você recebe de uma busca que rodou e não encontrou nada: document.querySelector('.total') numa página sem elemento .total. undefined é o que você recebe de algo que nunca foi definido: uma propriedade que não existe no objeto, uma variável que foi declarada e não recebeu valor, uma função que não retornou nada. null geralmente aponta para a página; undefined geralmente aponta para os dados.

De onde vem o undefined

A mensagem é a mesma em todos os casos. A causa está numa lista curta, e o nome da propriedade entre parênteses é uma boa pista sobre qual delas.

Os dados ainda não chegaram. A página foi renderizada antes que a requisição que a preenche voltasse, e o código leu response.items.length enquanto response ainda era o placeholder. Esse é o caso que aparece em conexões lentas e não em rápidas, num celular e não num notebook, para o cliente e nunca para o desenvolvedor. (reading 'length') e (reading 'map') são os sinais típicos: algo esperava um array e não recebeu nada.

A forma mudou. A API costumava retornar user.profile; agora ela retorna user.profile só para contas que preencheram um perfil, ou renomeou a chave, ou o campo subiu um nível. Nada mudou no cliente, e o erro começou no dia de um deploy de outra pessoa. Essa é a versão que chega como “funcionava ontem”.

A chave está escrita diferente. item.userId no código, item.user_id na resposta. Nada te avisa: o JavaScript lê uma propriedade que não existe como undefined e segue em frente, e o erro só aparece um passo depois, quando algo tenta ler através dela.

É uma lista vazia. results[0].title quando results é []. Código perfeitamente válido, até a primeira busca que não encontra nada. Dados de teste raramente estão vazios; dados de produção, muitas vezes, estão.

Algo não retornou nada. Uma função com um caminho de código que esquece de dar return, uma função async cujo chamador esqueceu de dar await nela e recebeu uma promise em vez de um valor, um .find() que não encontrou correspondência. Todos esses casos produzem undefined e passam ele adiante para a linha seguinte.

O código está minificado. Em produção a mensagem diz Cannot read properties of undefined (reading 'a'), porque a propriedade foi renomeada pelo build. O número da linha aponta para uma única linha enorme. Sem um source map, a mensagem parou de dizer qualquer coisa, e o que resta é a sequência de requisições e cliques que levou até ali.

Como isso parece do outro lado

Nada disso é visível para a pessoa que topou com o erro. O que ela vê é uma seção da página que nunca preencheu, um botão que não faz nada, um formulário que não envia. Nada na página diz “ocorreu um erro”, porque o erro aconteceu dentro de um script e o script simplesmente parou. A linha do console existe, num painel que ela nunca abriu.

Então o relato diz “a página de pedidos está em branco”. E o desenvolvedor reproduz o cenário, com uma conexão rápida, uma conta completa e uma lista não vazia, e a página não está em branco.

Fraco
Página de pedidos está em branco para um cliente, impossível reproduzir.
Melhor
Console: TypeError: Cannot read properties of undefined (reading 'items'), orders.js line 214. A requisição para /api/orders logo antes retornou 200 com corpo vazio. O cliente ainda não tem pedidos.

A segunda versão é uma correção prestes a acontecer. Tudo nela veio da página no momento da falha: a linha do console, a requisição anterior a ela, e a resposta que deveria conter os dados. Essa é a evidência de que esse erro precisa, e é exatamente a evidência que uma pessoa olhando para uma página em branco não consegue fornecer sozinha. Nosso guia sobre como escrever um relato de bug cobre o que pedir; a versão resumida é que, para esse erro, o console e o log de rede são o relato, e a descrição é só uma legenda.

Session Replay

Extensão gratuita para Chrome. Um clique na página com problema captura a tela, o console e o log de rede, e devolve um link para colar no ticket.

Baixe a extensão

Encontrando a causa nas ferramentas de desenvolvedor

Quando você tem a página na sua frente e o erro se reproduz, três movimentos resolvem o caso.

Pause nas exceções. No painel Sources do Chrome, o ícone de pausa com “Pause on uncaught exceptions” para o script na linha que lançou a exceção, com todas as variáveis ainda no escopo. Passe o mouse sobre a expressão e você vê qual parte de user.profile.name é undefined, o que a mensagem nunca teria te contado.

Olhe a requisição anterior. Vá até a aba Network e encontre a resposta que deveria ter preenchido o objeto. Em nove de dez vezes a resposta está ali: um corpo vazio, um 200 com uma mensagem de erro dentro, uma chave com nome diferente, ou uma requisição que nunca disparou porque estava esperando outra coisa. O artigo sobre failed to fetch cobre o caso em que a própria requisição morreu; este artigo é sobre o caso em que ela teve sucesso e retornou a forma errada.

Verifique a ordem dos eventos. Se o erro só aparece às vezes, é quase sempre a primeira causa acima: uma leitura que roda antes de os dados estarem lá. Limite a rede na aba Network para “Slow 3G” e recarregue. Se o erro passa a aparecer de forma confiável, é uma condição de corrida, e a correção é esperar pelos dados em vez de proteger a leitura.

A correção que não é uma correção

user?.profile?.name faz o erro sumir. Não faz o perfil aparecer. O optional chaining transforma um crash num undefined silencioso, que então flui para a página como uma string vazia, uma linha faltando, um botão sem rótulo, e agora o bug está invisível para você e ainda visível para o cliente.

É a ferramenta certa quando a ausência é legítima: uma conta que genuinamente ainda não tem perfil. É a ferramenta errada quando a ausência é o próprio bug. A pergunta a fazer antes de adicionar um ?. é se aquele valor deveria mesmo faltar algum dia. Se não deveria, o crash estava te dizendo alguma coisa, e uma proteção é uma forma de não escutar.

O que levar disso

A mensagem nomeia a propriedade que você pediu e esconde a coisa que estava vazia. Leia-a como “o passo anterior a esse não produziu nada” e vá olhar esse passo: a requisição, o valor de retorno, o nome da chave, a lista que estava vazia. A correção está sempre antes do número da linha, sem exceção.

E quando o relato vem da tela de outra pessoa, a linha do console e o log de rede não são contexto para o bug. Para esse erro, eles são o bug.