Ever find yourself writing defensive code like this? 🤔
// ❌ Verbose and repetitive
const userName =
user && user.profile && user.profile.name ? user.profile.name : "Anonymous";
const userEmail =
user && user.profile && user.profile.email ? user.profile.email : "";
const userAvatar =
user && user.profile && user.profile.avatar
? user.profile.avatar
: "/default-avatar.png";
Optional chaining (?.
) is your friend! Here’s the clean way:
// ✅ Clean and readable
const userName = user?.profile?.name ?? "Anonymous";
const userEmail = user?.profile?.email ?? "";
const userAvatar = user?.profile?.avatar ?? "/default-avatar.png";
Why This Matters
- Less code = fewer bugs
- More readable = easier to maintain
- Type-safe = TypeScript knows what’s happening
- Performance = short-circuits on first falsy value
Real React Example
interface User {
id: string;
profile?: {
name?: string;
email?: string;
avatar?: string;
};
settings?: {
theme?: 'light' | 'dark';
notifications?: boolean;
};
}
function UserCard({ user }: { user?: User }) {
return (
<div className="user-card">
<img
src={user?.profile?.avatar ?? '/default-avatar.png'}
alt={user?.profile?.name ?? 'User'}
/>
<h3>{user?.profile?.name ?? 'Anonymous User'}</h3>
<p>{user?.profile?.email ?? 'No email provided'}</p>
<span className={`theme-${user?.settings?.theme ?? 'light'}`}>
{user?.settings?.notifications ? '🔔' : '🔕'}
</span>
</div>
);
}
Try It Yourself
Copy this code into your TypeScript playground and experiment:
interface User {
id: string;
profile?: {
name?: string;
email?: string;
avatar?: string;
};
settings?: {
theme?: "light" | "dark";
notifications?: boolean;
};
}
// Test data - try changing these values!
const user1: User = {
id: "1",
profile: {
name: "Tony Sauvageau",
email: "tony@example.com",
avatar: "/tony-avatar.jpg",
},
settings: {
theme: "dark",
notifications: true,
},
};
const user2: User = {
id: "2",
// Missing profile and settings!
};
const user3: User | undefined = undefined;
// Your code here - try different approaches!
function getUserInfo(user?: User) {
// ❌ Old way (verbose)
// const name = user && user.profile && user.profile.name ? user.profile.name : 'Anonymous';
// ✅ New way (clean)
const name = user?.profile?.name ?? "Anonymous";
const email = user?.profile?.email ?? "No email";
const theme = user?.settings?.theme ?? "light";
return { name, email, theme };
}
// Test the function
console.log("User 1:", getUserInfo(user1));
console.log("User 2:", getUserInfo(user2));
console.log("User 3:", getUserInfo(user3));
Try these experiments:
- Change the user data and see how optional chaining handles missing properties
- Replace
??
with||
and see the difference with falsy values - Add more nested optional properties and chain them
Pro Tips
- Combine with nullish coalescing (
??
) for better defaults - Works with arrays too:
users?.[0]?.name
- Function calls:
user?.getFullName?.()
- TypeScript knows the types - no more
any
needed!
When NOT to Use
// ❌ Don't use when you expect the value to exist
const userId = user?.id; // If user exists, id should exist
// ✅ Better - let it fail fast
const userId = user.id;
The Bottom Line
Optional chaining eliminates defensive programming boilerplate. Your code becomes more readable, maintainable, and less prone to errors.
Pro tip: Combine with TypeScript’s strict null checks for maximum safety! 🚀
Want more TypeScript tips? Check out my TypeScript Patterns for React article for advanced patterns that separate junior from senior developers.