最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - Conditional types for a dynamic React component in Typescript - Stack Overflow

programmeradmin6浏览0评论

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 either AddEditJobs or AddEditCustomers depending on the value of props.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 of SelectComponent 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
Add a ment  | 

2 Answers 2

Reset to default 4

If 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.

发布评论

评论列表(0)

  1. 暂无评论