I have a React app using TypeScript 4.8.4
.
import { isNil } from "lodash"; // isNil(value: any): value is null | undefined
const order = ... // { uuid: string } | null
const { data } = useOrderQuery(
isNil(order?.uuid)
? { skip: true }
: { variables: { uuid: order.uuid } }, // variables requires { uuid: string }
);
If !isNil(order?.uuid)
, order.uuid
is a string
, which means that order
must not be null
(if it was null
, the optional chaining would produce undefined
for order.uuid
).
However, tsc
flags the order.uuid
access on the last line, saying error TS2531: Object is possibly 'null'.
I think this is because order.uuid
is a mutable location (I may be wrong). If that's the case, why does TypeScript think it's possible for the type to change in between the ternary condition and the false
branch of the ternary?
There are lots of changes to the condition that fix this (isNil(order) || isNil(order.uuid)
, test a new orderUuid
variable, order?.uuid === null || order?.uuid === undefined
, etc), but they're all more verbose.
VSCode doesn't complain (I think it's using the latest TypeScript version).
Upgrading my TypeScript version fixes this (this is fixed somewhere between 5.2.1
and 5.3.3
), but I'd love to find a fix that works for my current version, or at least understand the issue better.
You're encountering microsoft/TypeScript#35974 in which the effects of a custom type guard function did not propagate up through an optional chaining expression. It isn't that TypeScript thought it was "possible" for the narrowed value to get re-widened in the middle of your expression; it's that TypeScript didn't really perform the narrowing you were expecting at all. Such narrowings don't happen automatically in TypeScript merely by logical implication; they need to be explicitly implemented in the compiler.
And this narrowing was, indeed, implemented in microsoft/TypeScript#55613, which was released with TypeScript 5.3. So the recommended way to deal with this is to keep your version of TypeScript up to date. This isn't always easy, but the language moves fast enough that dependencies on a particular version of the language tends to accumulate technical debt quickly. If you're stuck on TypeScript 5.2 or below, then you'll need to modify your code in some way. The easiest approach is to just use a non-null assertion (!
) like { variables: { uuid: order!.uuid } }
and move on.