Reputation: 5059
I am trying to modularize my type casting. This is my code:
export enum detailsDataTypes {
MACHINE = 'MACHINE',
USER = 'USER',
ABSTRACT = 'ABSTRACT',
}
export type sharedTypes = {
name?: string;
OEECategory?: string;
OAECategory?: string;
color?: string;
type?: detailsDataTypes;
};
export type AbstractData = sharedTypes & {
layer: string;
};
export type UserData = sharedTypes & {
timeout: number;
stateAfterTimeout: string;
accessCanChoose: string;
accessCanOverride: string;
};
export type MachineData = sharedTypes & {
stateCategory: string;
timeout: number;
stateAfterTimeout: string;
accessCanChoose: string;
accessCanOverride: string;
canOverrideOnlyByStates: boolean;
};
type DetailsData = AbstractData | UserData | MachineData;
export const detailsDataCaster = (data: DetailsData): DetailsData | null => {
switch (data.type) {
case detailsDataTypes.ABSTRACT:
return <AbstractData>data;
case detailsDataTypes.MACHINE:
return <MachineData>data;
case detailsDataTypes.USER:
return <UserData>data;
default:
console.log('Wrong data type. Go yell at backend team or check your mocks');
return null;
}
};
What I'm trying to achieve, is tell TS that I'm returning AbstractData
OR UserData
OR MachineData
OR null
. But now, it doesn't like me doing this:
export const Details: FC<IDetails> = ({ data: $data = mockUserData }): ReactElement => {
const data = detailsDataCaster($data);
switch (data.type) {
case detailsDataTypes.ABSTRACT:
return <>Abstract</>;
case detailsDataTypes.MACHINE:
return <>Machine</>;
case detailsDataTypes.USER:
return <UserDisplay data={data} />; // error is here
default:
return <div />;
}
};
Error
Type 'DetailsData' is not assignable to type 'UserData'.
Type 'AbstractData' is not assignable to type 'UserData'.
Type 'AbstractData' is missing the following properties from type '{ timeout: number; stateAfterTimeout: string; accessCanChoose: string; accessCanOverride: string; }': timeout, stateAfterTimeout, accessCanChoose, accessCanOverride
As I understand |
does not mean OR in TS. I know there is the AND &
, but this would make no sense. I thought that if I cast, it would be clear as to what I'm returning, but I see it's not so simple. What am I missing?
Upvotes: 1
Views: 208
Reputation: 901
You might want to add a discriminant value to each type like this...
export type AbstractData = sharedTypes & {
layer: string;
type: detailsDataTypes.ABSTRACT; // add this field, it can only be of this literal value
};
export type UserData = sharedTypes & {
timeout: number;
stateAfterTimeout: string;
accessCanChoose: string;
accessCanOverride: string;
type: detailsDataTypes.USER; // add this field
};
export type MachineData = sharedTypes & {
stateCategory: string;
timeout: number;
stateAfterTimeout: string;
accessCanChoose: string;
accessCanOverride: string;
canOverrideOnlyByStates: boolean;
type: detailsDataTypes.MACHINE; // add this field
};
type DetailsData = AbstractData | UserData | MachineData;
function Details(data: DetailsData) {
switch (data.type) {
case detailsDataTypes.ABSTRACT:
data; // data is AbstractData
break;
case detailsDataTypes.MACHINE:
data; // data is MachineData
break;
case detailsDataTypes.USER:
data; // data is UserData
break;
default:
data; // data is never (no more possibilities left!)
break;
}
}
This technique is called discriminated unions.
Upvotes: 1
Reputation: 11545
If every type extending the sahredTypes
would specify its own type
the switch will magically work:
export enum detailsDataTypes {
MACHINE = 'MACHINE',
USER = 'USER',
ABSTRACT = 'ABSTRACT',
}
export interface sharedTypes {
name?: string;
OEECategory?: string;
OAECategory?: string;
color?: string;
type?: detailsDataTypes;
};
export interface AbstractData extends sharedTypes {
type: detailsDataTypes.ABSTRACT;
layer: string;
};
export interface UserData extends sharedTypes {
type: detailsDataTypes.USER;
timeout: number;
stateAfterTimeout: string;
accessCanChoose: string;
accessCanOverride: string;
};
export interface MachineData extends sharedTypes {
type: detailsDataTypes.MACHINE;
stateCategory: string;
timeout: number;
stateAfterTimeout: string;
accessCanChoose: string;
accessCanOverride: string;
canOverrideOnlyByStates: boolean;
};
type DetailsData = AbstractData | UserData | MachineData;
Playgound link that shows the type is correctly inferred from the switch.
Upvotes: 2