Cannot read properties of undefined: causes and fixes

TL;DR

"Cannot read properties of undefined" is the TypeError Chrome, Edge and Node.js throw when code reads a property from a value that is undefined. In order.totals.grand, it means order.totals was undefined, and the name in brackets is the property it couldn't read. The fix starts with finding out why that value was missing.

In this guide

What the error means

Here's the full message from Chrome:

text
TypeError: Cannot read properties of undefined (reading 'grand')

The name in brackets is the property the code tried to read, not the thing that was missing. People search their code for grand and stare at it. The problem is whatever sits to its left. In order.totals.grand, that's order.totals. Older versions of Chrome and Node worded it as Cannot read property 'grand' of undefined, and it means the same thing.

Firefox and Safari report the same mistake differently, and honestly more helpfully, because they name the expression that was undefined:

The message for order.totals.grand
Cannot read properties of undefined (reading 'grand')

If the message says of null instead, the value was null. That's often a DOM lookup, because document.querySelector and getElementById return null when nothing matches.

Why it happens in production

On your machine it works. In a customer's browser it doesn't. When this error arrives in a bug report, it's almost always one of four things.

The data hadn't loaded yet

The code ran before the thing it needed arrived. A component renders before its fetch resolves, or a click handler fires while a request is still in flight. Your laptop on office wifi wins that race every time. A customer on a train loses it.

The API sent a different shape

The code expected { totals: { grand: 12.5 } } and got something else. A backend change renamed a field, an optional field was missing for this one customer, or the request failed and the code parsed the error body as if it were the order. That last one is common, because fetch doesn't throw on a 401 or a 500. It resolves, res.json() happily returns { "message": "Unauthenticated" }, and the next line crashes.

A DOM element wasn't on the page

document.getElementsByClassName('promo-code')[0].value works until a customer gets the page without a promo field: a logged-out layout, an A/B test variant, a feature flag. The collection is empty, [0] is undefined, and reading .value throws.

The page came from an older deploy

Someone opened your app yesterday and never reloaded. Their tab is still running last week's JavaScript against today's API, or today's code is reading something an older version saved to localStorage in a different shape. These are the reports nobody can reproduce, because a fresh page load fixes them.

How to tell which cause it is

Don't start with a fix. Start with the evidence, because each cause leaves a different trail.

What the report showsWhat it points to
The error came seconds after page load or a click, with the request still pending or just finishedData not loaded yet
A failed request (401, 500) or a response missing the field, just before the errorThe API shape
The stack trace lands on a DOM lookup, and the customer was on an unusual page or variantA missing element
The tab had been open for hours or days, or the frames point at a bundle file you no longer serveAn older deploy

Start with the top frame in your own code. A production trace is minified, so you'll need a source map to read it. Then look at the network requests just before the error. If the order request returned a 401, you already have your answer, and the fix belongs in the error handling, not the rendering. Finally, read what the customer did. "I left it open over lunch and came back" tells you more than any amount of code reading.

If the report came through Rebase, the console error, the failed requests and the customer's last actions arrive together in the report (what gets captured). Otherwise, ask for a HAR file and the time it happened.

How to fix it

Optional chaining (?.) is the fix everyone reaches for, and it's right when a value is genuinely allowed to be missing. Used everywhere, it just turns a loud crash into a silent wrong answer: a total that reads as blank, a button that does nothing. I'd rather the page fail loudly with a clear message than quietly show a customer the wrong price.

JavaScript
// Allowed to be missing: say so, and give a default.const coupon = order.discount?.code ?? 'none';// Never allowed to be missing: fail early, with a message that names the cause.async function loadOrder(id) {  const res = await fetch(`/api/orders/${id}`);  if (!res.ok) throw new Error(`GET /api/orders/${id} returned ${res.status}`);  const order = await res.json();  if (!order.totals) throw new Error(`Order ${id} arrived without totals`);  return order;}

For data that hasn't loaded, render a loading state instead of guarding every property:

JavaScript
import { useEffect, useState } from 'react';function CartTotal({ id }) {  const [order, setOrder] = useState(null);  const [error, setError] = useState(null);  useEffect(() => {    loadOrder(id).then(setOrder, setError);  }, [id]);  if (error) return <p>We couldn't load your cart.</p>;  if (!order) return <p>Loading your cart…</p>;  return <p>Total: {order.totals.grand.toFixed(2)}</p>;}

For missing elements, check the lookup before you use it. For old deploys, version anything you save to localStorage and throw away data from a version you don't recognise.

For support teams

When a customer hits this, ask what they clicked and how long the tab had been open. Ask them to reload too. If a reload fixes it, tell engineering, because it points at an older deploy rather than at their account.

More on Console errors

Found this useful? Pass it on to your team.