Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React TypeScript component with two different prop interfaces

I want to create a React TypeScript component whose props is a union of two different interfaces. However, when I do so, I get the warning:

TS2339: Property 'color' does not exist on type 'PropsWithChildren<Props>'

How can I create a React TypeScript component with a union of two different prop interfaces and at the same time am able to destructure those props? Thanks!

sampleComponent.tsx:

import * as React from 'react';

interface SamplePropsOne {
  name: string;
}

interface SamplePropsTwo {
  color: string;
}

type Props = SamplePropsOne | SamplePropsTwo;

const SampleComponent: React.FC<Props> = ({ color, name }) => (
  color ? (
    <h1>{color}</h1>
  ) : (
    <h1>{name}</h1>
  )
);

export default SampleComponent;

enter image description here

like image 458
Jimmy Avatar asked Oct 30 '19 17:10

Jimmy


People also ask

Can I pass multiple props to component React?

You can pass as many props add you need into a component. you are passing the props to dump component. it's not react component. pass the props to dump as function argument.

How do you pass a component as a prop in React TypeScript?

To pass an object as props to a component in React TypeScript: Define an interface for the type of the object. Pass an object of the specified type to the child component, e.g. <Employee {... obj} /> .

Can we render two child components in React?

React allows us to render one component inside another component. It means, we can create the parent-child relationship between the 2 or more components. Prerequisites: The pre-requisites for this project are: React Knowledge.


1 Answers

Before TypeScript will let you read name or color off the union type, it needs some evidence that you're working with the correct type of Props (SamplePropsOne or SamplePropsTwo). There are a few standard ways to provide it with this.

One is by making the union a tagged union by introducing a property to distinguish branches of the union. This type checks just fine:

interface SamplePropsOne {
  type: 'one';
  name: string;
}

interface SamplePropsTwo {
  type: 'two';
  color: string;
}

type Props = SamplePropsOne | SamplePropsTwo;

const SampleComponent: React.FC<Props> = props => (
  props.type === 'one' ? (
    <h1>{props.name}</h1>
  ) : (
    <h1>{props.color}</h1>
  )
);

If you get the cases backwards (as I did when writing this!) then TypeScript will complain.

If the presence of a property is enough to distinguish the types, then you can use the in operator:

interface SamplePropsOne {
  name: string;
}
interface SamplePropsTwo {
  color: string;
}
type Props = SamplePropsOne | SamplePropsTwo;

const SampleComponent: React.FC<Props> = props => (
  'color' in props ? (
    <h1>{props.color}</h1>
  ) : (
    <h1>{props.name}</h1>
  )
);

If determining which type of object you have requires more complex logic, you can write a user-defined type guard. The key part is the "is" in the return type:

function isSampleOne(props: Props): props is SamplePropsOne {
  return 'name' in props;
}

const SampleComponent: React.FC<Props> = props => (
  isSampleOne(props) ? (
    <h1>{props.name}</h1>
  ) : (
    <h1>{props.color}</h1>
  )
);

It's also worth noting that because of the way structural typing works, there's no reason props in your example couldn't have both name and color:

const el = <SampleComponent name="roses" color="red" />;  // ok

If it's important to not allow this, you'll need to use some slightly fancier types:

interface SamplePropsOne {
  name: string;
  color?: never;
}
interface SamplePropsTwo {
  color: string;
  name?: never;
}
type Props = SamplePropsOne | SamplePropsTwo;

The ts-essentials library has an XOR generic that can be used to help construct exclusive unions like this.

like image 91
danvk Avatar answered Oct 11 '22 10:10

danvk