What happened
We hit a bug where a user's email did not show on one screen even though the data was complete. The cause was code that read user?.emial, one letter off. There was no error and no warning, and the screen simply rendered an empty value.
This kind of bug ships easily because nothing visibly breaks.
Why it happens
When user is any, the compiler allows access to any property, and the result is any as well. The ?. operator only prevents a throw when the value is null or undefined. It does not check the property name, and at runtime a missing property just evaluates to undefined rather than raising an error.
When the type is defined, for example User | null, writing user?.emial fails with an error saying the property does not exist on type User. So the real problem is not optional chaining but a type that is too loose. Sometimes we also sprinkle ?. everywhere simply because we are not sure what the data looks like.
What we do now
Type data from outside the app properly, either with an interface that matches the response or by validating it with a schema before use. When you do not know the real shape, use unknown instead of any so you are forced to check it first, and keep strict and noImplicitAny turned on.
When you see ?. stacked several levels deep, ask whether that data should really be optional. If not, fix the type, or handle the missing value once at the boundary of the system.
type User = { id: string; email: string; name?: string }
function show(a: any, b: User | null) {
a?.emial // no error: any allows everything
b?.emial // error: Property 'emial' does not exist on type 'User'
}Quick checklist
- 1Search for any in code that handles user data and API responses.
- 2Replace it with a real type, or with unknown plus validation.
- 3Turn on strict and noImplicitAny.
- 4Remove ?. where the type says the value is always present.
Rule of thumb
Type your data properly so the compiler can catch typos, and treat heavy optional chaining as a sign to revisit the data model.