Problem
You have these types:
type OptionalData<T> = T extends undefined ? { data?: any } : { data: T };
export type DataObject<T = any> = {
item?: string | null;
} & OptionalData<T>;
When you type a variable as DataObject with the type parameter being a union of a type (such as string) and undefined you expect the data property to be typed as the union type however it is typed as any:
const data: DataObject<string | undefined> = { data: ... }
// data.data should be typed as `string | undefined` however
// data.data is typed as `any`
What is happening?
TypeScript's documentation for conditional types is useful here. It reads:
Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation. For example, an instantiation of T extends U ? X : Y with the type argument A | B | C for T is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y).
ā Documentation on distributive conditional types
So in your case it resolves to
| (string extends undefined ? { data?: any } : { data: T })
| (undefined extends undefined ? { data?: any } : { data: T })
The first conditional resolves to { data: T } and the second resolves to { data?: any }. I believe union types will resolve to the type that is the most generic so the type is eventually { data?: any }.
Solution
Here's OptionalData updated so that it works as expected:
type OptionalData<T> = [T] extends [undefined] ? { data?: any } : { data: T };
We just wrapped T and undefined with square brackets ([]), turning them into tuples (or technically monuple because it's only got one element). Here's the demo.
Why does this work?
Let's look back at the documentation for conditional types again, with some added emphasis on the important parts:
Conditional types in which the checked type is a naked type parameter are called distributive conditional types. Distributive conditional types are automatically distributed over union types during instantiation.
As we turned those naked types to tuples they are not naked anymore. And because they are no longer naked they are no longer distributed.