javascript
examples
examples.js⚡javascript
/**
* =====================================================
* 3.10 OPTIONAL CHAINING OPERATOR - EXAMPLES
* =====================================================
* The ?. operator for safe property access
*/
// =====================================================
// 1. THE PROBLEM: ACCESSING NESTED PROPERTIES
// =====================================================
console.log('--- The Problem ---');
const user1 = {
name: 'Alice',
profile: {
email: 'alice@example.com',
avatar: {
url: 'https://example.com/alice.png',
size: 256,
},
},
};
const user2 = {
name: 'Bob',
// No profile property
};
const user3 = null;
// Safe access with user1 (full object)
console.log('User1 avatar:', user1.profile.avatar.url);
// "https://example.com/alice.png"
// Unsafe access with user2 - would throw error!
// console.log(user2.profile.avatar.url);
// TypeError: Cannot read property 'avatar' of undefined
// Traditional solution (verbose)
if (user2 && user2.profile && user2.profile.avatar) {
console.log(user2.profile.avatar.url);
} else {
console.log('User2 avatar: No avatar');
}
// =====================================================
// 2. BASIC PROPERTY ACCESS WITH ?.
// =====================================================
console.log('\n--- Basic Property Access ---');
// Optional chaining makes it simple
console.log('User1 avatar (?.): ', user1?.profile?.avatar?.url);
// "https://example.com/alice.png"
console.log('User2 avatar (?.): ', user2?.profile?.avatar?.url);
// undefined (no error!)
console.log('User3 avatar (?.): ', user3?.profile?.avatar?.url);
// undefined (no error!)
// Short-circuit happens at first null/undefined
const result1 = user1?.profile?.avatar?.url; // Full chain evaluated
const result2 = user2?.profile?.avatar?.url; // Stops at profile (undefined)
const result3 = user3?.profile?.avatar?.url; // Stops at user3 (null)
console.log('Results:', result1, result2, result3);
// =====================================================
// 3. BRACKET NOTATION WITH ?.[]
// =====================================================
console.log('\n--- Bracket Notation ---');
const data = {
users: {
'user-1': { name: 'Alice', role: 'admin' },
'user-2': { name: 'Bob', role: 'editor' },
},
settings: ['dark', 'compact', 'notifications'],
};
// Dynamic property access
const userId = 'user-1';
console.log('User by ID:', data?.users?.[userId]?.name);
// "Alice"
const missingId = 'user-99';
console.log('Missing user:', data?.users?.[missingId]?.name);
// undefined
// Array element access
console.log('First setting:', data?.settings?.[0]);
// "dark"
console.log('Missing setting:', data?.settings?.[99]);
// undefined
console.log('Missing array:', data?.themes?.[0]);
// undefined
// Computed property names
const propName = 'users';
console.log('Via computed:', data?.[propName]?.['user-2']?.role);
// "editor"
// =====================================================
// 4. METHOD CALLS WITH ?.()
// =====================================================
console.log('\n--- Method Calls ---');
const calculator = {
add: (a, b) => a + b,
multiply: (a, b) => a * b,
// No subtract method
};
// Safe method calls
console.log('add exists:', calculator.add?.(5, 3));
// 8
console.log('subtract exists:', calculator.subtract?.(5, 3));
// undefined (no error!)
// Object with optional callback
const config = {
onSuccess: (data) => console.log('Success:', data),
// No onError callback
};
config.onSuccess?.('Data loaded'); // "Success: Data loaded"
config.onError?.('Something failed'); // Nothing happens, no error
// =====================================================
// 5. COMBINING ALL THREE FORMS
// =====================================================
console.log('\n--- Combined Usage ---');
const app = {
api: {
endpoints: {
users: '/api/users',
posts: '/api/posts',
},
handlers: {
users: {
get: (id) => ({ id, name: 'User ' + id }),
list: () => [1, 2, 3],
},
},
},
};
// Combining property, bracket, and method access
const endpoint = app?.api?.endpoints?.['users'];
console.log('Endpoint:', endpoint); // "/api/users"
const user = app?.api?.handlers?.users?.get?.(42);
console.log('User:', user); // { id: 42, name: "User 42" }
const list = app?.api?.handlers?.users?.list?.();
console.log('List:', list); // [1, 2, 3]
// Missing paths
const missing = app?.api?.handlers?.posts?.get?.(1);
console.log('Missing handler:', missing); // undefined
// =====================================================
// 6. ONLY NULL AND UNDEFINED TRIGGER SHORT-CIRCUIT
// =====================================================
console.log('\n--- Only null/undefined Short-Circuit ---');
const obj = {
zero: 0,
empty: '',
falseVal: false,
nullVal: null,
undefVal: undefined,
};
// These DO NOT short-circuit (not null/undefined)
console.log('zero?.toString():', obj.zero?.toString?.()); // "0"
console.log('empty?.length:', obj.empty?.length); // 0
console.log('falseVal?.toString():', obj.falseVal?.toString?.()); // "false"
// These DO short-circuit (null or undefined)
console.log('nullVal?.prop:', obj.nullVal?.prop); // undefined
console.log('undefVal?.prop:', obj.undefVal?.prop); // undefined
// Comparison with && operator
console.log('\n--- Comparison with && ---');
console.log(
'obj.zero && obj.zero.toString():',
obj.zero && obj.zero.toString()
); // 0 (stops!)
console.log('obj.zero?.toString():', obj.zero?.toString?.()); // "0" (works!)
// =====================================================
// 7. COMBINING WITH NULLISH COALESCING (??)
// =====================================================
console.log('\n--- With Nullish Coalescing ---');
const userSettings = {
theme: null, // Explicitly null
// fontSize not defined
notifications: false, // Explicitly false
};
// Provide defaults for missing/null values
const theme = userSettings?.theme ?? 'light';
const fontSize = userSettings?.fontSize ?? 16;
const notifications = userSettings?.notifications ?? true;
console.log('Theme:', theme); // "light" (null replaced)
console.log('Font size:', fontSize); // 16 (undefined replaced)
console.log('Notifications:', notifications); // false (kept - not null/undefined)
// Deep optional access with defaults
const deepConfig = {
server: {
// port not defined
},
};
const port = deepConfig?.server?.port ?? 3000;
console.log('Port:', port); // 3000
const host = deepConfig?.server?.host ?? 'localhost';
console.log('Host:', host); // "localhost"
// Missing entire chain
const timeout = deepConfig?.database?.connection?.timeout ?? 5000;
console.log('Timeout:', timeout); // 5000
// =====================================================
// 8. PRACTICAL EXAMPLES
// =====================================================
console.log('\n--- Practical Examples ---');
// Example 1: API Response Handling
const apiResponse = {
status: 'success',
data: {
user: {
id: 1,
profile: {
name: 'John Doe',
social: {
twitter: '@johndoe',
},
},
},
},
};
const userName = apiResponse?.data?.user?.profile?.name ?? 'Anonymous';
const twitter =
apiResponse?.data?.user?.profile?.social?.twitter ?? 'Not provided';
const github =
apiResponse?.data?.user?.profile?.social?.github ?? 'Not provided';
console.log('Name:', userName); // "John Doe"
console.log('Twitter:', twitter); // "@johndoe"
console.log('GitHub:', github); // "Not provided"
// Example 2: Event Handler Configuration
function setupButton(config) {
const button = { click: () => console.log('Button clicked') };
// Safe callback invocation
config?.onSetup?.('Button is being set up');
button.click = () => {
console.log('Button clicked!');
config?.onClick?.();
config?.analytics?.track?.('button_click');
};
return button;
}
const btn1 = setupButton({
onSetup: (msg) => console.log('Setup:', msg),
onClick: () => console.log('Custom click handler!'),
});
btn1.click();
const btn2 = setupButton(null); // No config, no error
btn2.click();
// Example 3: Array of Objects
console.log('\n--- Array of Objects ---');
const products = [
{ id: 1, name: 'Laptop', specs: { ram: 16, storage: 512 } },
{ id: 2, name: 'Phone' }, // No specs
null, // Null entry
];
products.forEach((product, index) => {
const name = product?.name ?? 'Unknown';
const ram = product?.specs?.ram ?? 'N/A';
console.log(`Product ${index}: ${name}, RAM: ${ram}`);
});
// Output:
// Product 0: Laptop, RAM: 16
// Product 1: Phone, RAM: N/A
// Product 2: Unknown, RAM: N/A
// Example 4: Function with Optional Config
function createServer(options) {
const config = {
port: options?.port ?? 3000,
host: options?.host ?? 'localhost',
ssl: options?.ssl?.enabled ?? false,
sslCert: options?.ssl?.cert ?? null,
cors: options?.cors?.origins ?? ['*'],
logging: options?.logging?.level ?? 'info',
};
console.log('Server config:', config);
return config;
}
// All these work without errors
createServer({ port: 8080 });
createServer({ ssl: { enabled: true, cert: 'cert.pem' } });
createServer(undefined);
createServer(null);
// =====================================================
// 9. DELETE WITH OPTIONAL CHAINING
// =====================================================
console.log('\n--- Delete with Optional Chaining ---');
const objToModify = {
a: {
b: {
c: 'value',
},
},
};
// You can use ?. with delete
delete objToModify?.a?.b?.c;
console.log('After delete:', objToModify); // { a: { b: {} } }
// Safe delete on potentially missing path
const maybeNull = null;
delete maybeNull?.prop; // No error, does nothing
console.log('Delete on null: no error');
// =====================================================
// 10. WITH DESTRUCTURING
// =====================================================
console.log('\n--- With Destructuring ---');
const response = {
data: {
user: {
name: 'Alice',
preferences: {
theme: 'dark',
},
},
},
};
// Get nested value with optional chaining, then destructure
const preferences = response?.data?.user?.preferences ?? {};
const { theme = 'light', language = 'en' } = preferences;
console.log('Theme:', theme); // "dark"
console.log('Language:', language); // "en"
// With null response
const nullResponse = null;
const nullPrefs = nullResponse?.data?.user?.preferences ?? {};
const { theme: t2 = 'light' } = nullPrefs;
console.log('Null theme:', t2); // "light"
// =====================================================
// 11. IN LOOPS AND ITERATIONS
// =====================================================
console.log('\n--- In Loops ---');
const items = [
{ id: 1, meta: { tags: ['js', 'web'] } },
{ id: 2 }, // No meta
{ id: 3, meta: {} }, // Empty meta
null, // Null item
{ id: 5, meta: { tags: ['python'] } },
];
// Safe iteration
for (const item of items) {
const id = item?.id ?? 'unknown';
const firstTag = item?.meta?.tags?.[0] ?? 'untagged';
console.log(`Item ${id}: ${firstTag}`);
}
// Using filter and map with optional chaining
const taggedItems = items
.filter((item) => item?.meta?.tags?.length > 0)
.map((item) => ({
id: item.id,
firstTag: item.meta.tags[0],
}));
console.log('Tagged items:', taggedItems);
// =====================================================
// 12. CHAINING COMPARISON
// =====================================================
console.log('\n--- Chaining Methods Comparison ---');
const testObj = {
a: {
b: {
c: {
value: 42,
},
},
},
};
// Traditional
let traditional;
if (testObj && testObj.a && testObj.a.b && testObj.a.b.c) {
traditional = testObj.a.b.c.value;
}
console.log('Traditional:', traditional); // 42
// Optional chaining
const optChain = testObj?.a?.b?.c?.value;
console.log('Optional chain:', optChain); // 42
// With nullish coalescing for default
const withDefault = testObj?.a?.b?.c?.missing ?? 'default';
console.log('With default:', withDefault); // "default"
// =====================================================
// SUMMARY
// =====================================================
console.log('\n--- Summary of Optional Chaining ---');
console.log(`
Optional Chaining Syntax:
obj?.prop - Property access
obj?.[expr] - Bracket notation
obj?.method() - Method call
Key Points:
• Returns undefined if any part is null/undefined
• Only null and undefined trigger short-circuit
• Other falsy values (0, "", false) don't trigger it
• Combine with ?? for default values
• Cannot be used for assignment (left-hand side)
• Great for API responses, configs, and callbacks
`);