Usually this happens because TypeScript’s control-flow analysis can’t prove that the value you checked is the same value you’re using later.
For example:
const user = getUser();
if (user) {
console.log(user.name); // fine
}
But things can get tricky with properties:
if (obj.user) {
doSomething();
}
console.log(obj.user.name); // possibly undefined
TypeScript doesn't necessarily assume obj.user is still defined because doSomething() (or other code) could have changed it.
Common cases
1. You checked a property, then accessed it again
if (foo.bar) {
foo.bar.baz; // may complain
}
Use a local variable:
const bar = foo.bar;
if (bar) {
bar.baz; // TypeScript knows bar is defined
}
2. A function call happens between the check and use
if (foo.value) {
someFunction();
foo.value.doSomething(); // possibly undefined
}
TypeScript is conservative because someFunction() could mutate foo.value.
3. The check doesn't actually narrow the expression
For example:
if (arr.length > 0) {
console.log(arr[0].name); // arr[0] can still be undefined
}
An array having a positive length doesn't always narrow indexed access under TypeScript's rules.
Use:
const item = arr[0];
if (item) {
console.log(item.name);
}
4. Optional chaining isn't a narrowing check
if (foo?.bar) {
// ...
}
foo.bar.baz; // may still complain
Again, storing the value locally is often the cleanest fix.
If you paste the exact code that TypeScript is complaining about, I can explain precisely why it isn't narrowing it in that case.