This runtime error occurs when your code attempts to access a property or method on a value that is null. It's one of the most common JavaScript/TypeScript runtime errors and can be prevented using null checks, optional chaining, or TypeScript's strict null checking.
This is a runtime TypeError that occurs when you try to access a property, call a method, or use any member of a value that evaluates to null. Unlike compile-time TypeScript errors, this error happens during code execution. The error message follows the pattern "Cannot read properties of null (reading 'propertyName')" where 'propertyName' is the specific property you attempted to access. This error is nearly identical to "Cannot read properties of undefined" but specifically indicates the value was explicitly null rather than undefined. Common scenarios include: 1. **DOM element not found**: Using `document.getElementById()` or `querySelector()` that returns null because the element doesn't exist 2. **Asynchronous data**: Accessing properties on data that hasn't loaded yet from an API call 3. **Uninitialized object properties**: Accessing nested properties when intermediate objects are null 4. **Array methods**: Using methods like `.find()` which return null when no match is found, then accessing properties on the result The error occurs because null is a primitive value with no properties or methods, so any attempt to read from it throws a TypeError.
The simplest fix is to check if a value is null or undefined before accessing its properties:
// BEFORE: Error - Cannot read properties of null
const user = getUserById(id); // Returns null if not found
console.log(user.name);
// AFTER: Check for null first
const user = getUserById(id);
if (user !== null) {
console.log(user.name);
} else {
console.log("User not found");
}For DOM elements:
// BEFORE: Error if element doesn't exist
const button = document.getElementById("submit-btn");
button.addEventListener("click", handleClick);
// AFTER: Null check
const button = document.getElementById("submit-btn");
if (button !== null) {
button.addEventListener("click", handleClick);
}You can also use combined null/undefined checks:
if (user != null) { // Checks both null and undefined
console.log(user.name);
}Optional chaining (introduced in TypeScript 3.7 / ES2020) safely accesses nested properties and short-circuits to undefined if any step is null:
// BEFORE: Error if user or address is null
const city = user.address.city;
// AFTER: Returns undefined instead of throwing error
const city = user?.address?.city;Combined with nullish coalescing (??), you can provide default values:
// Return "Unknown" if user, address, or city is null/undefined
const city = user?.address?.city ?? "Unknown";
// Array method example
const firstItem = items?.find(item => item.id === 5);
const itemName = firstItem?.name ?? "Not found";
// DOM element example
const buttonText = document.getElementById("btn")?.textContent;For optional method calls:
// Only call if handler exists
handler?.onClick?.();
// Array methods
const uppercaseNames = users?.map(u => u.name.toUpperCase());TypeScript's strictNullChecks compiler option catches null/undefined errors at compile time:
// tsconfig.json
{
"compilerOptions": {
"strict": true, // Includes strictNullChecks
// OR enable specifically:
"strictNullChecks": true
}
}With strict null checks enabled, TypeScript will error if you don't handle null cases:
// TypeScript will show error: Object is possibly 'null'
const element = document.getElementById("myId");
element.addEventListener("click", handler); // Error!
// Fix: Add null check or use optional chaining
const element = document.getElementById("myId");
element?.addEventListener("click", handler); // OK
// Or with explicit check
if (element !== null) {
element.addEventListener("click", handler); // OK
}You can also use type narrowing:
function processUser(user: User | null) {
if (user === null) {
return; // Early return
}
// TypeScript knows user is not null here
console.log(user.name);
}Ensure your script runs after the DOM is ready. Common patterns:
Move script tag to end of body:
<!DOCTYPE html>
<html>
<body>
<button id="myButton">Click me</button>
<!-- Script runs AFTER elements are rendered -->
<script src="app.js"></script>
</body>
</html>Use DOMContentLoaded event:
document.addEventListener("DOMContentLoaded", () => {
const button = document.getElementById("myButton");
if (button !== null) {
button.addEventListener("click", handleClick);
}
});In React, use useEffect for DOM access:
import { useEffect } from "react";
function MyComponent() {
useEffect(() => {
const element = document.getElementById("external-element");
if (element !== null) {
element.style.color = "blue";
}
}, []); // Runs after component mounts
return <div>Component</div>;
}Check if selector is correct:
// Returns null if ID is wrong
const btn = document.getElementById("sumbit-btn"); // Typo!
// Should be:
const btn = document.getElementById("submit-btn");For data loaded asynchronously, initialize with null and check before rendering:
React example with loading states:
function UserProfile({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetchUser(userId)
.then(data => setUser(data))
.finally(() => setLoading(false));
}, [userId]);
if (loading) return <div>Loading...</div>;
if (user === null) return <div>User not found</div>;
// Safe to access user properties here
return <div>{user.name}</div>;
}Vanilla TypeScript with async/await:
async function displayUserData(userId: string) {
const user = await fetchUser(userId);
if (user === null) {
console.log("User not found");
return;
}
// Safe to use user here
console.log(user.name);
updateUI(user);
}Provide default values:
const response = await fetch("/api/config");
const config = await response.json() ?? { theme: "light" };
console.log(config.theme); // Safe, always has defaultTypeScript's non-null assertion operator (!) tells the compiler "I know this isn't null." Use sparingly and only when you're certain:
// Tell TypeScript this element definitely exists
const button = document.getElementById("myButton")!;
button.addEventListener("click", handleClick);Warning: This bypasses TypeScript's safety checks. If the element doesn't exist, you'll still get the runtime error. Only use when:
1. You're absolutely certain the value isn't null
2. The element is guaranteed to exist (e.g., in a specific HTML template)
3. You've already validated it exists earlier in the code
Better alternatives:
// Instead of non-null assertion, use type guard
function initializeButton() {
const button = document.getElementById("myButton");
if (button === null) {
throw new Error("Button element not found"); // Fail fast
}
// TypeScript knows button is not null here
button.addEventListener("click", handleClick);
}Or use optional chaining for graceful handling:
// Doesn't throw, just skips if null
document.getElementById("myButton")?.addEventListener("click", handleClick);### Difference Between null and undefined
Both cause similar errors, but they have different meanings:
- null: Explicitly assigned to indicate "no value" or "empty"
- undefined: Variable declared but not initialized, or property doesn't exist
let userA = null; // Intentionally empty
let userB; // Undefined (no assignment)
let userC = undefined; // Explicitly undefined
const obj = { name: "John" };
console.log(obj.age); // undefined (property doesn't exist)### TypeScript Type Guards and Narrowing
TypeScript can infer when null has been checked:
function processUser(user: User | null) {
// Type is User | null here
if (user === null) {
return;
}
// TypeScript narrows type to User (not null) here
console.log(user.name); // No error, TypeScript knows user isn't null
}Custom type guards for complex checks:
function isValidUser(user: User | null): user is User {
return user !== null && user.id !== undefined;
}
const user = getUserById(5);
if (isValidUser(user)) {
console.log(user.name); // TypeScript knows user is User here
}### React Specific: Props and State
Common React scenarios where null appears:
// Props with optional children
interface Props {
children?: React.ReactNode; // Can be undefined
}
function Container({ children }: Props) {
// Error if you don't check
return <div>{children.toUpperCase()}</div>;
// Fix: Check or use optional chaining
return <div>{children ?? "No content"}</div>;
}### Array.find() Returns undefined (Not null)
Note that .find() returns undefined, not null, but the error is the same:
const users = [{ id: 1, name: "Alice" }];
const user = users.find(u => u.id === 999); // Returns undefined
console.log(user.name); // Error: Cannot read properties of undefined
// Fix
const user = users.find(u => u.id === 999);
if (user !== undefined) {
console.log(user.name);
}
// Or use optional chaining
console.log(user?.name);### Nullish Coalescing vs Logical OR
The ?? operator only checks for null/undefined, while || checks for any falsy value:
const count = 0;
// Logical OR treats 0 as falsy
const result1 = count || 10; // 10 (wrong if 0 is valid)
// Nullish coalescing only checks null/undefined
const result2 = count ?? 10; // 0 (correct)Use ?? when 0, "", or false are valid values.
### Server-Side Rendering (SSR) Considerations
In Next.js or SSR environments, window/document might not exist:
// Causes error on server
const width = window.innerWidth; // Error: window is not defined
// Fix: Check if running in browser
const width = typeof window !== "undefined" ? window.innerWidth : 0;
// Or use useEffect (client-side only)
useEffect(() => {
const width = window.innerWidth; // Safe in useEffect
}, []);Function expression requires a return type
Function expression requires a return type
Value of type 'string | undefined' is not iterable
How to fix "Value is not iterable" in TypeScript
Type 'undefined' is not assignable to type 'string'
How to fix "Type undefined is not assignable to type string" in TypeScript
Type narrowing from typeof check produces 'never'
How to fix "Type narrowing produces never" in TypeScript
Type parameter 'T' has conflicting constraints
How to fix "Type parameter has conflicting constraints" in TypeScript