Trying to create a dynamic React ponent. What is the proper way to conditionally pass the correct propType based on the selected ponent. Here's what I have so far I'm getting an error on this line <SelectComponent {...props.props} />
because the props don't match the ponents:
export interface SidebarProps {
type: keyof typeof ponents,
props: AddEditJobsProps | AddEditCustomersProps
}
const ponents = {
job: AddEditJobs,
customer: AddEditCustomers
};
export default function Sidebar(props: SidebarProps) {
const { open, toggle } = useToggle();
const SelectComponent = ponents[props.type];
return (
<RightSidebar open={open} toggleDrawer={toggle}>
<SelectComponent {...props.props} />
</RightSidebar>
);
}
Edit: adding any to the props fixes the error however Typescript won't be able to match the selected type with the corresponding props during type checking which is what I'm hoping to acplish here.
export interface SidebarProps {
type: keyof typeof ponents,
props: AddEditJobsProps | AddEditCustomersProps | any
}
Trying to create a dynamic React ponent. What is the proper way to conditionally pass the correct propType based on the selected ponent. Here's what I have so far I'm getting an error on this line <SelectComponent {...props.props} />
because the props don't match the ponents:
export interface SidebarProps {
type: keyof typeof ponents,
props: AddEditJobsProps | AddEditCustomersProps
}
const ponents = {
job: AddEditJobs,
customer: AddEditCustomers
};
export default function Sidebar(props: SidebarProps) {
const { open, toggle } = useToggle();
const SelectComponent = ponents[props.type];
return (
<RightSidebar open={open} toggleDrawer={toggle}>
<SelectComponent {...props.props} />
</RightSidebar>
);
}
Edit: adding any to the props fixes the error however Typescript won't be able to match the selected type with the corresponding props during type checking which is what I'm hoping to acplish here.
export interface SidebarProps {
type: keyof typeof ponents,
props: AddEditJobsProps | AddEditCustomersProps | any
}
Share
Improve this question
edited Feb 10, 2020 at 20:41
user3376065
asked Feb 7, 2020 at 23:53
user3376065user3376065
1,1871 gold badge13 silver badges31 bronze badges
4
-
So
SelectComponent
could be eitherAddEditJobs
orAddEditCustomers
depending on the value ofprops.type
? – jered Commented Feb 8, 2020 at 0:12 -
What is your desired behavior? Are you just trying to get rid of the error? Or do you want Typescript to actually infer the type of
props.props
? The former might be possible, but the latter is impossible because the actual type ofSelectComponent
will be unknown until runtime and can not be inferred by Typescript. – jered Commented Feb 8, 2020 at 0:22 - @jered good questions, I don't care about runtime, my desired behavior is to get good feedback from typescript during coding, so when I pass the "type" to my dynamic sidebar ponent I get to see what "props" I need to pass as well. – user3376065 Commented Feb 8, 2020 at 0:42
- Link to a similar problem with a nice solution, – Tasos Tsournos Commented Sep 27, 2022 at 6:08
2 Answers
Reset to default 4If you insist on keeping it dynamic that way, here's how you should probably do it.
Problem number one, as I mentioned in my ment, is that TS does not know what the value of props.type
will be until runtime, so it cannot effectively infer what it should be ahead of time. To solve this, you need something like a plain old conditional that will explicitly render the correct ponent:
export const Sidebar: React.FC<SidebarProps> = props => {
const { open, toggle } = useToggle();
let inner: React.ReactNode;
if (props.type === "job") {
inner = <AddEditJobs {...props.props} />;
} else if (props.type === "customer") {
inner = <AddEditCustomers {...props.props} />;
}
return (
<RightSidebar open={open} toggleDrawer={toggle}>
{inner}
</RightSidebar>
);
};
Note that the conditional basically asserts what the value of that field will be, which helps TS infer what the rest of the shape will be and improves code-time type checking.
Problem number two is that your interface for SidebarProps
is too permissive.
You have this:
export interface SidebarProps {
type: keyof typeof ponents,
props: AddEditJobsProps | AddEditCustomersProps
}
This is basically telling Typescript "the object should have a type
field matching one of the keys in ponents
, and a props
field that is either AddEditJobsProps
or AddEditCustomersProps
". But it's not specifying that if type
equals "job"
then the props
field must match AddEditJobsProps
. To do that you need to more explicitly say so:
export type SidebarProps =
| {
type: "job";
props: AddEditJobsProps;
}
| { type: "customer"; props: AddEditCustomersProps };
This uses union types to ensure that SidebarProps
has either one or the other plete and valid shapes.
With these changes, not only does the TS error in Sidebar
go away, but when you render it in another ponent you will get the expected proper TS checking. If type
is "job"
but the props
prop does not have the expected shape of AddEditJobsProps
, you will get an error. Try it for yourself in this sandbox:
https://codesandbox.io/s/youthful-fog-8lq41
I think the answer is that your "dynamic ponent" pattern here is making things difficult. It confusing for both humans and puters to understand, resulting in code that is hard to read and maintain, and that the Typescript piler can't accurately check.
A better pattern to use here would be ponent position. Make <Sidebar>
more generic so that it doesn't care what children it renders, and only handles the open/toggle state.
export default const Sidebar: React.FC = ({children}) => {
const { open, toggle } = useToggle();
return (
<RightSidebar open={open} toggleDrawer={toggle}>
{children}
</RightSidebar>
);
}
Then, when you want to render a sidebar you just give it the children that you want it to wrap:
<Sidebar>
<AddEditJobs {...addEditJobsProps} />
</Sidebar>
// or
<Sidebar>
<AddEditCustomers {...addEditCustomersProps} />
</Sidebar>
You will get accurate and strict type checking (because TS will know the exact type of ponents and props) and the structure of your code will be more readable and easier to follow.