简体   繁体   中英

Conditional types for a dynamic React component in Typescript

Trying to create a dynamic React component. What is the proper way to conditionally pass the correct propType based on the selected component. 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 components:

export interface SidebarProps {
    type: keyof typeof components,
    props: AddEditJobsProps | AddEditCustomersProps
}

const components = {
  job: AddEditJobs,
  customer: AddEditCustomers
};

export default function Sidebar(props: SidebarProps) {
  const { open, toggle } = useToggle();
  const SelectComponent = components[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 accomplish here.

export interface SidebarProps {
    type: keyof typeof components,
    props: AddEditJobsProps | AddEditCustomersProps | any
}

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 comment, 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 component:

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 components,
    props: AddEditJobsProps | AddEditCustomersProps
}

This is basically telling Typescript "the object should have a type field matching one of the keys in components , 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 complete and valid shapes.

With these changes, not only does the TS error in Sidebar go away, but when you render it in another component 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 component" pattern here is making things difficult. It confusing for both humans and computers to understand, resulting in code that is hard to read and maintain, and that the Typescript compiler can't accurately check.

A better pattern to use here would be component composition. 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 components and props) and the structure of your code will be more readable and easier to follow.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM