Your explanation refers to the concept of Action<T>
as a distributive conditional type. In this scenario, unions within T
are broken down into individual elements (e.g., B | C | D
), processed through Action<T>
, and then aggregated back into a new union (e.g.,
Action<B> | Action<C> | Action<D>
). Although this behavior is commonly desired in conditional types, it was used unintentionally in your case.
A conditional type structured like TTT extends UUU ? VVV : WWW
will only be distributive if the type being evaluated, TTT
, is a plain generic type parameter such as the T
in your definition of Action<T>
. If it's a specific type (like string
or Date
), it won't behave distributively. Similarly, complex expressions involving a type parameter (such as {x: T}
) also prevent distribution. I often refer to the latter idea, where you modify the "bare" type parameter T
like {x: T}
, as "clothing" the type parameter.
In your example, T extends undefined
is distributive:
type Action<T> = T extends undefined ? ... : ...
To disable distribution, you can rephrase the check so that T
is clothed but still behaves the same way (so the validation passes only if T
extends
undefined</code). The simplest approach is to enclose both sides of the <code>extends
clause within one-element
tuple types), making
T
become
[T]
and
undefined
turn into
[undefined]
:
type Action<T> = [T] extends [undefined] ? ... : ...
This adjustment makes your code work as intended:
function handleApiResponse<T>(apiResponse: ApiResponse<T>) {
const a: Action<ApiResponse<T>> = {
type: "response",
payload: apiResponse,
} // okay
}
Note that TypeScript considers tuples and arrays to be covariant in their element types, meaning that
Array<X> extends Array<Y>
or
[X] extends [Y]
if and only if
X extends Y
. While technically risky (refer to this question for more details), this covariance proves to be highly practical.
A useful rule in such cases is to transform an accidentally distributive expression like TTT extends UUU ? VVV : WWW
by enclosing it within brackets to make it non-distributive: [TTT] extends [UUU] ? VVV : WWW
. This technique, mentioned towards the end of the documentation on distributive conditional types, provides a solution without requiring any additional syntax changes. Despite appearing somewhat unique due to the square brackets, it simply utilizes existing one-element tuple syntax effectively.
Playground link to code