I'm not entirely sure how to ask this question, or what lingo to use, so I'm going to give this my best shot.
I want to write a component that works on an array of items, and will call a function render each item. I don't want to specify the type of the item, or the arguments to the render function, but I do want to make sure they match. Here's an example of what I'd like to do:
type MyComponentProps<ItemType = any> = {
items: ItemType[];
renderItem: (item: ItemType, index: number) => React.ReactElement;
};
const MyComponent = ({ items, renderItem }: MyComponentProps) => {
return <div>{items.map((item, index) => renderItem(item, index))}</div>;
};
Taking this example component, I'd like to be able to use it like this:
const App = () => {
const items: string[] = ['foo', 'bar'];
const renderItem = (item: string, index: number) => <div>{item}</div>;
// This should be happy, and work fine
return <MyComponent items={items} renderItem={renderItem} />;
}
However, I'd like this to fail:
const App = () => {
const items: number[] = [1, 2];
const renderItem = (item: string, index: number) => <div>{item}</div>;
// This should generate a type error
return <MyComponent items={items} renderItem={renderItem} />;
}
Should I be able to do something like this? I bet there is a way to make MyComponent
generic, and then make a specific instance of that component where I need it for each ItemType
I want to pass in. But I also think there might be a way to make the renderItem
type refer to the items
type somehow to make sure they all line up.
CodePudding user response:
You were really close. All you need to do is to add a generic type to the MyComponent
function.
type MyComponentProps<ItemType> = {
items: ItemType[];
renderItem: (item: ItemType, index: number) => React.ReactElement;
};
const MyComponent = <T,>({ items, renderItem }: MyComponentProps<T>) => {
return <div>{items.map((item, index) => renderItem(item, index))}</div>;
};
T
will be inferred as the type passed for items
. If this does not match with item
in the function parameters, an error is shown.
CodePudding user response:
You're so close @peterw! Here's all you had to change:
/* notice that we're using 'extends' instead of '=' here */
type MyComponentProps<ItemType extends any> = {
items: ItemType[]
renderItem: (item: ItemType, index: number) => React.ReactElement
}
/* notice that we're making this generic */
const MyComponent = <ItemType extends any>({
items,
renderItem,
}: MyComponentProps<ItemType>) => {
return <div>{items.map((item, index) => renderItem(item, index))}</div>
}
const App1 = () => {
const items: string[] = ['foo', 'bar']
const renderItem = (item: string) => <div>{item}</div>
return <MyComponent items={items} renderItem={renderItem} /> // no errors here
}
const App2 = () => {
const items: number[] = [1, 2]
const renderItem = (item: string) => <div>{item}</div>
return <MyComponent items={items} renderItem={renderItem} /> // Error: Type 'number[]' is not assignable to type 'string[]'.
}