javascript

examples

examples.js
/**
 * =====================================================
 * 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
`);
Examples - JavaScript Tutorial | DeepML