Optional Chaining and Nullish Coalescing
Safe property access with optional chaining (?.) and nullish coalescing (??)
Challenge
Section titled “Challenge”You’re working with nested API responses that may have missing properties at any level. How do you safely access deeply nested values without multiple null checks?
Explanation
Section titled “Explanation”Optional chaining allows safe property access when properties might not exist. Nullish coalescing provides defaults only for null/undefined, unlike the || operator which treats falsy values as missing.
Solution
Section titled “Solution”// ❌ Old approach - multiple checksconst user = apiResponse?.data?.user;if (user !== null && user !== undefined) { const email = user.email;}
// ✅ Optional chaining - safe accessconst email = apiResponse?.data?.user?.email;console.log(email); // undefined if any level is null/undefined
// ✅ Nullish coalescing - provide defaultconst displayName = user.name ?? 'Anonymous';// Uses default only if name is null/undefined, not if it's empty string or 0
// Compare with ||const width = size || 100; // Uses default if size is 0, '', falseconst width2 = size ?? 100; // Uses default only if size is null/undefinedOptional Chaining Operators
Section titled “Optional Chaining Operators”const user = { name: 'Alice', address: { street: '123 Main St', city: 'NYC' }, getEmail: () => 'alice@example.com'};
// Property accessconsole.log(user?.name); // 'Alice'console.log(user?.phone); // undefined
// Nested propertyconsole.log(user?.address?.zipCode); // undefined
// Method callingconsole.log(user?.getEmail?.()); // 'alice@example.com'console.log(user?.getPhone?.()); // undefined (method doesn't exist)
// Array indexingconst items = ['a', 'b'];console.log(items?.[0]); // 'a'console.log(items?.[10]); // undefinedPractical Examples
Section titled “Practical Examples”// API response handlingasync function processUserData(apiResponse) { // Safe nested access const userEmail = apiResponse?.data?.user?.email; const phoneNumber = apiResponse?.data?.user?.phone?.number; const preferences = apiResponse?.data?.user?.settings?.notifications ?? true;
// Safe method calls const userId = apiResponse?.data?.getUser?.().id;
return { email: userEmail ?? 'no-email', phone: phoneNumber ?? 'no-phone', preferences };}
// DOM manipulationfunction setButtonState(buttonId, disabled) { const button = document.getElementById(buttonId); button?.addEventListener?.('click', handleClick); button?.classList?.toggle?.('disabled', disabled); button?.setAttribute?.('disabled', disabled);}
// Conditional logic with coalescingconst config = { retries: 0, timeout: null};
const maxRetries = config.retries ?? 3; // Uses 3 (0 is falsy but not nullish)const timeoutMs = config.timeout ?? 5000; // Uses 5000 (null is nullish)Short-Circuiting Behavior
Section titled “Short-Circuiting Behavior”const obj = { a: null, b: { c: 'value' }};
// Short-circuits at first null/undefinedconsole.log(obj.a?.b?.c); // undefined (stops at obj.a which is null)
// Won't cause error even if property doesn't existconst result = obj?.x?.y?.z; // undefined (doesn't throw)
// Useful in error casesfunction handleApiError(error) { const errorMessage = error?.response?.data?.message ?? 'Unknown error'; const statusCode = error?.response?.status ?? 500;
console.log(`[${statusCode}] ${errorMessage}`);}Combining with Logical Operators
Section titled “Combining with Logical Operators”// Optional chaining + logical ANDconst permissions = user?.permissions;if (permissions && permissions.includes('admin')) { // Safe: permissions is checked before using includes()}
// Nullish coalescing + logical ORconst theme = localStorage.getItem('theme') ?? (prefersDark ? 'dark' : 'light');
// Multiple null coalesceconst port = process.env.PORT ?? process.env.DEFAULT_PORT ?? 3000;Edge Cases
Section titled “Edge Cases”// Optional chaining doesn't call getters on the wayconst obj = { get prop() { console.log('getter called'); return undefined; }, nested: { value: 42 }};
console.log(obj?.prop?.nested); // 'getter called' THEN undefined// Getter is called even though result is undefined
// Nullish coalescing vs logical operatorsconst x = false ?? 'default'; // false (false is not nullish)const y = false || 'default'; // 'default' (false is falsy)
const a = 0 ?? 100; // 0 (0 is not nullish)const b = 0 || 100; // 100 (0 is falsy)
const s = '' ?? 'empty'; // '' (empty string is not nullish)const t = '' || 'empty'; // 'empty' (empty string is falsy)