Your app crashes with “Cannot read property ‘name’ of undefined”? Optional chaining (?.) safely accesses nested properties without try-catch blocks.
The Error-Prone Old Way:
const user = getUserData(); // Might return null
// ❌ This crashes if user is null/undefined
const name = user.profile.name;
// TypeError: Cannot read property 'profile' of undefined
// ❌ Traditional defensive code (ugly)
const name = user && user.profile && user.profile.name;
// ❌ Or with try-catch (expensive)
let name;
try {
name = user.profile.name;
} catch (e) {
name = 'Unknown';
}
The Modern Solution – Optional Chaining:
const user = getUserData(); // ✅ Safe access with ?. const name = user?.profile?.name; // If user is null/undefined → name = undefined // If user.profile is null/undefined → name = undefined // If user.profile.name exists → name = actual value // No errors, no crashes!
How It Works:
// Short-circuits at first null/undefined
const result = obj?.prop1?.prop2?.prop3;
// Execution stops immediately if any part is null/undefined
// Think of it as:
if (obj === null || obj === undefined) {
return undefined;
} else if (obj.prop1 === null || obj.prop1 === undefined) {
return undefined;
} else if (obj.prop1.prop2 === null || obj.prop1.prop2 === undefined) {
return undefined;
} else {
return obj.prop1.prop2.prop3;
}
// But written in 1 clean line!
With Default Values:
// Combine with ?? (nullish coalescing) const name = user?.profile?.name ?? 'Guest'; // If any part is null/undefined, use 'Guest' // Difference from || operator: const count = user?.stats?.count || 10; // count = 10 if count is 0, false, '', etc. (BAD!) const count = user?.stats?.count ?? 10; // count = 10 ONLY if count is null/undefined (GOOD!) // Preserves 0, false, ''
Array Access:
const users = getUsers(); // Might return null // ❌ Crashes if users is null const firstUser = users[0]; // ✅ Safe with ?. const firstUser = users?.[0]; // ✅ Deep array access const firstName = users?.[0]?.profile?.name;
Function Calls:
const api = getAPI(); // Might be undefined // ❌ Crashes if api or api.getData is undefined const data = api.getData(); // ✅ Safe optional call const data = api?.getData?.(); // Only calls if both api exists AND getData is a function // Returns undefined if either is missing
Real-World Example – API Response:
// API might return partial data or null
const response = await fetch('/api/user/123').then(r => r.json());
// ❌ Old way (prone to crashes)
const street = response.data.user.address.street;
const phone = response.data.user.contact.phone;
// ✅ New way (bulletproof)
const street = response?.data?.user?.address?.street ?? 'N/A';
const phone = response?.data?.user?.contact?.phone ?? 'No phone';
// Handles:
// - API returns null
// - User doesn't exist (data is null)
// - User has no address
// - Address has no street
// All gracefully without crashes!
Conditional Execution:
// Execute function only if it exists
onSuccess?.(); // Calls onSuccess() if it exists, does nothing if undefined
// Pass arguments
onUserLoad?.(userId, userData);
// Method chaining
user
?.getProfile?.()
?.updateName?.('John')
?.save?.();
// Each step executes only if previous step succeeded
Delete Operations:
// Safe delete delete user?.profile?.tempData; // Only deletes if user and profile exist // No error if they don't
Limitations to Know:
// ❌ Can't use on left side of assignment
user?.name = 'John'; // SyntaxError!
// ✅ Check existence first
if (user) {
user.name = 'John';
}
// ❌ Can't use with 'new'
const instance = new User?.(); // SyntaxError!
// ✅ Check constructor exists
const instance = User && new User();
Performance Considerations:
// Benchmark: 1 million accesses // Traditional null checks const result = obj && obj.a && obj.a.b && obj.a.b.c; // Time: 125ms // Optional chaining const result = obj?.a?.b?.c; // Time: 120ms // Nearly identical performance! // But much more readable
Browser Support:
Supported in: ✅ Chrome 80+ (2020) ✅ Firefox 74+ (2020) ✅ Safari 13.1+ (2020) ✅ Edge 80+ (2020) ✅ Node.js 14+ = 97% of users worldwide For older browsers, Babel transpiles it: obj?.a?.b → obj == null ? undefined : obj.a == null ? undefined : obj.a.b
