Reputation: 434
I have two functional components which have two different prop types.
export interface OrderImageProps {
orders: ICartItem[]
}
const OrderImage: React.FC<OrderImageProps> = (props: OrderImageProps) => {
return (
<div>
// show some list
</div>
);
};
and
export interface ProductImageProps {
src: string
}
const ProductImage: React.FC<ProductImageProps> = (props: ProductImageProps) => {
return (
<Image className='img-fluid admin-product-image' src={props.src}/>
);
};
These two components are entities of a table, but conditionally rendered. So I need to pass these two components to the table component.
interface CustomDataTableProps {
nameTag: string
itemTag: string
priceTag: string
categoryTag: string
dateTag: string
actionsTag: string
image: React.FC<ProductImageProps | OrderImageProps>
list: boolean
}
const CustomDataTable: React.FC<CustomDataTableProps> = (props: CustomDataTableProps) => {
const cartItems: ICartItem[] = useSelector((state: RootState): ICartItem[] => (state.cart.cartItems));
const sampleData = sampleProducts.map((product: ICartItem) => {
return {
image: React.createElement(props.image, props.list ?
{
orders: cartItems
} :
{
src: product.src
}),
// more data are in here
};
}
But when passing props to the CustomDataTable
, it shows FC<ProductImageProps> is not assignable to type FC<ProductImageProps | OrderImageProps>
.
I understand this error, clearly they are incompatible types.
From my understanding may be these props need an XOR
behavior on React.FC<>
type.
This can easily be done by providing a render prop
on CustomDataTable
. Also there are other table entities which have like this kind of conditionally rendered components.
Can my first approach be achieved or is it better if I use a render prop?
Upvotes: 0
Views: 121
Reputation: 8380
React.FC
is essentially a function type with extra steps. And type parameter P
in React.FC<P>
goes in negative/contravariant position.
When you try to pass image
prop into CustomDataTable
it checks function compatibility and when it comes to arguments it check them in reverse direction. When you pass it an argument with type React.FC<ProductImageProps>
it doesn't check whether ProductImageProps
is assignable to ProductImageProps | OrderImageProps
but whether ProductImageProps | OrderImageProps
is assignable to ProductImageProps
. That it's clearly not.
Changing your image
prop type to React.FC<ProductImageProps> | React.FC<OrderImageProps>
will get rid of previous ... is not assignable ...
error. But you'll get another error at React.createElement
call site.
Now your new props.image
has type React.FC<ProductImageProps> | React.FC<OrderImageProps>
. But this type accepts only arguments of type ProductImageProps & OrderImageProps
. i.e. having all required fields from both your prop types.
type PropsA = { a: string }
type PropsB = { b: string }
type A = (propsA: PropsA) => void
type B = (propsB: PropsB) => void
type AB = A | B
function fnFromAB(ab: AB): void { // ab has type (args: PropsA & PropsB) => void
ab({ a: 'string' }) // error
ab({ b: 'string' }) // error
ab({ a: 'string ', b: 'string' }) // no error
}
The solution is to make type of image
component depend on type of it's props. And since type of props depends on list
flag you may rewrite your code as:
type CustomDataTableProps = {
nameTag: string
itemTag: string
priceTag: string
categoryTag: string
dateTag: string
actionsTag: string
} & ({
image: React.FC<OrderImageProps>
list: true
} | {
image: React.FC<ProductImageProps>
list: false
})
const CustomDataTable: React.FC<CustomDataTableProps> = (props: CustomDataTableProps) => {
const cartItems: ICartItem[] = useSelector((state: RootState): ICartItem[] => (state.cart.cartItems));
const sampleData = sampleProducts.map((product: ICartItem) => {
const image = props.list
? React.createElement(props.image, { orders: cartItems })
: React.createElement(props.image, { src: product.src })
return {
image,
// more data are in here
};
}
Unfortunately you'll have to duplicate React.createElement
call since props.image
has different types in each of the arms of ternary operator.
You may cut some corners with type assertions ofcourse. But for the cost of type safety. As usual.
Upvotes: 1