Blog>

Optional Chaining (?.) in JavaScript: Eliminate Property Errors

5 minute read

Optional Chaining (?.) in JavaScript: Eliminate Property Errors

If you've ever seen the dreaded "Cannot read property of undefined" error in JavaScript, you're going to love optional chaining.

Imagine you're working with data from an API that looks like this:

const user = { name: 'Sarah', address: { street: '123 Main St', city: 'New York' } };

If you want to get the user's city, you'd write:

const city = user.address.city; // 'New York'

But what happens if the address doesn't exist? Your code crashes:

const user = { name: 'Sarah' // No address! }; const city = user.address.city; // ERROR: Cannot read property 'city' of undefined

Before optional chaining, you had to write defensive code like this:

const city = user && user.address && user.address.city;

Or even worse:

let city; if (user) { if (user.address) { city = user.address.city; } }

This gets messy fast, especially with deeply nested data.

Optional chaining lets you safely access nested properties without all those checks. Just add ?. instead of .:

const city = user?.address?.city;

That's it! If user or address is undefined or null, the expression stops and returns undefined instead of crashing. No error, no mess.

When you fetch data from an API, you often don't know exactly what you'll get back:

// API might return incomplete data const blogPost = { title: 'My First Post', author: { name: 'Alex' // No social media info } }; // Safe way to get Twitter handle const twitter = blogPost?.author?.socialMedia?.twitter; console.log(twitter); // undefined (no error!) // You can provide a default value const twitterHandle = blogPost?.author?.socialMedia?.twitter || '@anonymous'; console.log(twitterHandle); // '@anonymous'

You can also use optional chaining to call functions that might not exist:

const user = { name: 'Jordan', // logout function might not always exist }; // Old way if (user.logout) { user.logout(); } // New way with optional chaining user.logout?.(); // Only calls if logout exists

This is super useful for event handlers:

// Call a callback only if it was provided function fetchData(url, onSuccess) { fetch(url) .then(response => response.json()) .then(data => { onSuccess?.(data); // Safely call the callback }); } // Works with or without callback fetchData('/api/users'); // No error fetchData('/api/users', (data) => console.log(data)); // Callback runs

Optional chaining works great with arrays too:

const users = [ { name: 'Alice', settings: { theme: 'dark' } }, { name: 'Bob' // No settings } ]; // Get theme for each user safely users.forEach(user => { const theme = user?.settings?.theme || 'light'; console.log(`${user.name} uses ${theme} theme`); }); // Output: // Alice uses dark theme // Bob uses light theme

You can even use it with array indices:

const firstUserCity = users[0]?.address?.city; const tenthUserCity = users[10]?.address?.city; // undefined, not an error

Optional chaining makes form handling much cleaner:

function submitForm(formData) { const email = formData?.contact?.email; const phone = formData?.contact?.phone; if (!email && !phone) { alert('Please provide at least one contact method'); return; } // Process form... } // Works with incomplete data submitForm({ name: 'Test' }); // Shows alert, doesn't crash submitForm({ contact: { email: 'test@example.com' } }); // Works fine

Optional chaining pairs perfectly with the nullish coalescing operator (??) to provide default values:

const user = { name: 'Sam', preferences: { notifications: false } }; // Get notification setting or default to true const notifications = user?.preferences?.notifications ?? true; console.log(notifications); // false // Compare with a user who has no preferences const newUser = { name: 'Jordan' }; const newUserNotifications = newUser?.preferences?.notifications ?? true; console.log(newUserNotifications); // true (uses default)

Important: Use ?? instead of || when you want to keep false or 0 as valid values. The || operator treats these as falsy and uses the default instead.

Here are the most common scenarios where optional chaining shines:

1. Reading Configuration

const theme = config?.ui?.theme?.primaryColor ?? '#007bff';

2. Accessing User Data

const userName = currentUser?.profile?.displayName ?? 'Guest';

3. Working with Events

function handleClick(event) { const clickedElement = event?.target?.dataset?.id; if (clickedElement) { // Do something } }

4. Conditional Rendering

// In a template or React component const displayEmail = user?.contactInfo?.email || 'No email provided';

Optional chaining is supported in all modern browsers:

  • Chrome 80+ (2020)

  • Firefox 74+ (2020)

  • Safari 13.1+ (2020)

  • Edge 80+ (2020)

If you're using a build tool like Webpack or Vite with Babel, it will automatically convert optional chaining for older browsers.

Do:

  • Use optional chaining when accessing nested properties that might not exist

  • Combine with ?? for default values

  • Use it for optional function calls

  • Chain multiple levels: a?.b?.c?.d

Don't:

  • Overuse it everywhere - if you know a property always exists, use regular dot notation

  • Use it on the left side of an assignment: user?.name = 'Bob' (doesn't work)

  • Forget that it returns undefined, not null

Here's how optional chaining makes real code cleaner:

function createUserCard(user) { return { name: user?.name ?? 'Anonymous', email: user?.contact?.email ?? 'No email', phone: user?.contact?.phone ?? 'No phone', address: user?.location?.address ?? 'Address not provided', avatar: user?.profile?.avatarUrl ?? '/default-avatar.png', isVerified: user?.verification?.status === 'verified', memberSince: user?.joinedDate ?? 'Recently joined' }; } // Works with complete data const fullUser = { name: 'Taylor', contact: { email: 'taylor@example.com', phone: '555-0100' }, location: { address: '456 Oak Ave' }, profile: { avatarUrl: '/taylor.jpg' }, verification: { status: 'verified' }, joinedDate: '2024-01-15' }; console.log(createUserCard(fullUser)); // Also works with minimal data - no errors! const minimalUser = { name: 'Casey' }; console.log(createUserCard(minimalUser));

Optional chaining is one of those features that immediately makes your code better. It's easy to learn, reduces bugs, and makes your everything more readable. Start using ?. today and say goodbye to those pesky "Cannot read property" errors!

The next time you're tempted to write if (obj && obj.prop && obj.prop.nested), remember: there's a better way with ?.

Happy coding!