Home > Blockchain >  How do I specify the type of a prop to refer to the type of another prop?
How do I specify the type of a prop to refer to the type of another prop?

Time:07-06

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.

Playground

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[]'.

}

  • Related