ScreenshotNeo

BlogHow-to

How to Fix a TypeError in JavaScript

Trace a JavaScript TypeError to the operation and value that caused it. Learn how to handle null, undefined, wrong types, and common debugging paths.

By the ScreenshotNeo team4 October 20268 min read

A JavaScript TypeError means the runtime could not perform an operation, commonly because a value was not the type the operation expected. There is no universal fix: read the full error, find the failing expression, inspect the values it uses, then handle or correct the value according to what the program should do.

For example, if a property access fails because its object is null or undefined, guard that access only if absence is valid. If the value is required, find why it is missing and fix that source instead of hiding the defect with a fallback.

1. Read the error and locate the operation

Start with the complete console message, including the filename, line, and column. Browser engines can describe similar failures in different words. “Cannot read properties of undefined (reading ‘x’)” and “undefined has no properties,” for instance, both point toward property access on an absent value. Treat the wording as a clue; inspect the actual expression before choosing a repair.

  1. Reproduce the action that triggers the error.
  2. Open the reported source location, using the source map if your code is bundled or transpiled.
  3. Identify the exact operation: property read or write, method call, destructuring, conversion, argument use, or attempted mutation.
  4. Inspect the operands and arguments immediately before that operation.
  5. Check spelling and capitalization, where the value came from, and what type and shape the API expects.
  6. Choose handling that matches the data contract, then repeat the same action and check relevant edge cases.

Use a breakpoint and the debugger’s scope view when possible. A focused log just before the failing line is also useful:

console.log({ value, type: typeof value, isArray: Array.isArray(value) });

Remember that typeof null is "object", so use an explicit null check when distinguishing it from an object. Logging helps reveal the runtime value; it does not establish why that value arrived. Trace the upstream lookup, state, response, or function contract to determine the cause.

2. Handle null and undefined deliberately

null and undefined do not have properties. If either can legitimately be present, test for both before property access. This explicit check preserves valid falsy values such as 0, false, and the empty string.

function getDisplayName(user) {
  if (user !== null && user !== undefined) {
    return user.name;
  }
  return "Guest";
}

console.log(getDisplayName({ name: "Ada" })); // "Ada"
console.log(getDisplayName(null));             // "Guest"

Use optional chaining when the operation is optional and returning undefined for missing data is meaningful:

const city = user?.address?.city;

Optional chaining prevents that property access from throwing, but it does not make required data valid. If the application must have a user or address, validate it and report the missing value rather than quietly continuing with undefined.

A truthiness check is shorter, but it also treats 0, false, "", and NaN as absent. Use it only when all falsy values should take the same branch:

if (value) {
  // Runs only when value is truthy.
}

For a deliberate nullish fallback, use ??; unlike ||, it keeps 0 and "":

const pageSize = settings.pageSize ?? 20;
const label = settings.label || "Untitled";

In this example, the first expression defaults only for null or undefined. The second defaults for every falsy label, including the empty string. Pick based on the intended behavior.

3. Check the kind and shape of the value

A value can be present but still incompatible with the operation. Before calling a method, verify that the value has the expected method and that the input matches the function’s contract.

function formatTags(tags) {
  if (!Array.isArray(tags)) {
    throw new TypeError("tags must be an array");
  }
  return tags.map((tag) => String(tag).trim()).filter(Boolean);
}

console.log(formatTags([" news ", "engineering"]));

Throwing a clear error at a boundary can be better than allowing a confusing failure deeper in the code. For external data, validate the shape after parsing:

function parseUser(jsonText) {
  const value = JSON.parse(jsonText);
  if (value === null || typeof value !== "object" || typeof value.name !== "string") {
    throw new TypeError("Expected an object with a string name");
  }
  return value;
}

Choose checks that reflect the actual contract. typeof is useful for primitives and functions, but arrays also report "object"; use Array.isArray for arrays. Avoid converting a value simply to silence an error unless conversion is part of the desired behavior.

4. Common TypeError patterns

Pattern What to inspect Appropriate response
Reading a property from null or undefined Lookup result, initialization order, response shape, or state at that point Guard optional absence, or correct/validate the required source
Calling a value that is not a function Whether the property exists, spelling/case, reassignment, and API contract Call the documented function or handle the case where it is unavailable
Using an incompatible argument Actual argument value and the operation’s accepted types Validate at the boundary or pass the intended value
Destructuring an absent value Whether the container was returned or initialized Supply an intentional default only when absence is valid; otherwise fix the source
Mutating a value that cannot be changed Whether it is frozen, read-only, or otherwise immutable Create a new value or update it through the owning API
Wrong data shape Whether an array, object, string, or nested field was expected Validate the shape and handle invalid data explicitly

These are diagnostic categories, not a diagnosis of your particular code. The stack location and runtime value determine which applies.

5. Debugging checklist

  1. Capture the full message. Keep the first error and stack trace; later errors may be consequences of the initial failure.
  2. Reproduce it consistently. Note the input and action that lead to the failing path.
  3. Inspect the exact expression. Break a long chain such as response.user.profile.name into intermediate values and inspect each one.
  4. Check assumptions. Verify spelling, capitalization, initialization order, API inputs, and data shape.
  5. Fix at the right boundary. Validate external data when it enters the program; keep internal invariants clear.
  6. Confirm behavior. Repeat the failing action and check valid edge cases, especially 0, false, and "" if a guard or fallback changed.
  7. Use a linter. A JavaScript linter can flag some likely mistakes before runtime. The browser debugger and console help inspect runtime values that static checks cannot determine.

6. Troubleshooting common messages

Message or symptom Likely meaning What to do
Cannot read properties of undefined (reading 'x') The expression before .x evaluated to undefined. Inspect that base value immediately before the access. Trace why it is missing; guard only if missing data is expected.
Cannot read properties of null / null has no properties The base value is null; wording varies by engine. Check whether the lookup or response can return null. Handle that case or correct the source contract.
x is not a function The value being called is not callable, perhaps due to a wrong property or unexpected value. Inspect x, spelling and capitalization, and the API documentation for the expected call.
Error occurs only for some records Those inputs may have a different or incomplete shape. Inspect a failing record and validate optional fields or reject malformed required data explicitly.
Line points into bundled code The browser is reporting generated output. Use source maps and reproduce with the original source location and same input.

Do not assume a message alone identifies the correct repair. Similar wording can arise from different paths, and engine wording differs. Follow the failing expression back to its value.

7. Performance, reliability, and maintenance

A null check or type check is usually negligible compared with network and rendering work, but adding broad defensive branches everywhere can make contracts harder to understand. Validate data at boundaries where it enters, use explicit invariants inside the program, and handle optional values close to where absence matters. This makes failures easier to locate and avoids silently converting invalid state into plausible output.

Reliability depends on choosing the right failure behavior: optional data may be skipped or defaulted intentionally; required data should produce a useful error or be repaired upstream. When debugging intermittent cases, record safe, relevant input context without exposing secrets or personal data. A linter and focused checks for missing and falsy values can catch regressions; no single guard can verify every upstream assumption.

8. Capture the page where the error appears

If the TypeError appears in a browser page and you need a reproducible visual record, capture the page alongside the console diagnosis. ScreenshotNeo is a website screenshot API and MCP server. Its screenshot capture is useful for recording what the page showed at the point you investigated; a screenshot does not replace the console message, stack trace, or runtime value.

Or skip the browser setup

To capture a page without configuring a browser automation stack, make one request to the ScreenshotNeo API. This example saves a WebP capture of the page where you reproduced the error:

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://example.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);

Before capture, ScreenshotNeo accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each of those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the page verdict and billing status in headers. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000. Every feature is on every plan.

Sign up for 1,000 free screenshots a month, with no card required.

9. Frequently asked questions

Is every TypeError caused by null or undefined?

No. Those are common cases, but a TypeError can also come from an incompatible argument or operand, a non-callable value, or an operation on a value that cannot be changed. Inspect the operation and actual value.

Should I add optional chaining wherever a property access fails?

No. Use it when missing data is expected and the optional result is useful. For required data, validate or repair the source so the failure is visible.

Why does the same problem have different error wording?

JavaScript engines can phrase similar failures differently. The expression and value at the reported location are more useful than matching a message word for word.

Can a linter find every TypeError?

No. Linting can catch some likely mistakes, but errors that depend on runtime data still require inspecting the failing execution.

Sources