I have an enum:
export enum Sections {
Dashboard = "Dashboard",
Select = "Select",
Widget = "Widget",
}
I want to build a prop for my react components that is a discriminating union type.
export type ReduxLocation =
| {
path: Sections;
widget?: never;
}
| {
path: Sections.Widget;
widget: {
id: string;
side?: "left" | "right";
};
};
Then one of the props of a react component would be of type ReduxLocation
:
const MyComponent: React.FC<{ reduxLocation: ReduxLocation }> = () => {
// return whatever
}
The goal is that if passing a reduxLocation
with a path
of any of the Sections
, no other entry is required in the reduxLocation
object. However, if path
is Sections.Widget
, then the widget
entry is required.
const App = () => {
return (
<div>
<MyComponent
reduxLocation={{
path: Sections.Dashboard
}}
/>
<MyComponent
reduxLocation={{
path: Sections.Dashboard,
widget: { id: 'id' } // <---- errors as expected
}}
/>
<MyComponent
reduxLocation={{
path: Sections.Widget,
widget: { id: 'id' }
}}
/>
<MyComponent
reduxLocation={{
path: Sections.Widget
// <-------------------------- should error but doesnt
}}
/>
</div>
);
}
In the case of the last MyComponent
above, I am not getting the desired TS error that when using a path: Sections.Widget
, the widget
property is then also required. My guess is because using path: Sections.Widget
also satisfies the first type in ReduxLocation
, as path
is still of type Maps
.
How can I achieve the desired result, where the above case enforces the correct combination of properties in the prop?
CodePudding user response:
The object { path: Sections.Widget }
is totally valid since Sections.Widget
is assignable to Sections
which fulfills the constraints of the first union element.
You would want to exclude Sections.Widget
from the path
of the first element. That way, the union can be propertly discriminated since each Section
is only assignable to one union element.
export type ReduxLocation =
| {
path: Exclude<Sections, Sections.Widget>;
widget?: never;
}
| {
path: Sections.Widget;
widget: {
id: string;
side?: "left" | "right";
};
};