简体   繁体   中英

Is it possible to create a generic React list component with typescript?

I am creating a list component that is supposed to handle different object to list. But I cant find a solution on rendering the item. The list needs to be generic because I have many different things to list, but maybe there is no way and I need to create different List Components that handles every Object?

I get this error:

Property 'name' does not exist on type 'string | PrivilegeConditionOperator | PrivilegeConditionElement | Privilege'.
  Property 'name' does not exist on type 'string'.

This is the code:

const GetItem = (
    item: string | PrivilegeConditionOperator | PrivilegeConditionElement | Privilege,
    index: number
) => {
    switch (typeof item) {
        case typeof PrivilegeConditionElement:
            return `PrivilegeContionElement ${index}`;
        case typeof Privilege:
            return `Privilege ${index}`;
        case typeof PrivilegeCondition:
            return `PrivilegeContion ${index}`;
        case typeof PrivilegeConditionOperator:
            return item.name; //<===============ERROR when doing this
        default:
            return item;
    }
};

const List: FC<Props> = ({ handelRemove, itemsList }) => (
    <ListWrapper>
        <ul style={{ listStyleType: "none", padding: "0", margin: 0 }}>
            {itemsList.map((item, index) => (
                <DivWithToolTip
                    data-tooltip={item}
                    // eslint-disable-next-line react/no-array-index-key
                    key={index}
                    style={{
                        display: "flex",
                        justifyContent: "row",
                        alignContent: "center",
                        alignItems: "center",
                    }}>
                    <li style={{ marginTop: "10px" }}>{GetItem(item, index)}</li>
                    <PlusButton
                        style={{ marginTop: "10px" }}
                        {...{ addIcon: false }}
                        onClick={() => handelRemove(item)}
                    />
                </DivWithToolTip>
            ))}
        </ul>
    </ListWrapper>
);

interface IListProps {
    handelRemove: (item: string | PrivilegeConditionOperator | PrivilegeConditionElement | Privilege) => void;
    itemsList: Array<string | PrivilegeConditionOperator | PrivilegeConditionElement | Privilege>;
}
type Props = IListProps;

export default List;

I have also tried this, but same error:

<li style={{ marginTop: "10px" }}>{item.name && item}</li>

What you have is no problem that's specific to react but rather to TypeScript as the TypeScript compiler complains about something in the GetItem function which is not react specific.

Assuming PrivilegeConditionOperator is a class, rather than an interface or type you could use if... else if... statements instead of switch and instanceof instead of typeof like this:

const GetItem = (
    item: string | PrivilegeConditionOperator | PrivilegeConditionElement | Privilege,
    index: number
) => {
        if (item instanceof PrivilegeConditionElement)
            return `PrivilegeContionElement ${index}`;
        else if (item instanceof Privilege)
            return `Privilege ${index}`;
        else if (item instanceof PrivilegeCondition)
            return `PrivilegeContion ${index}`;
        else if (item instanceof PrivilegeConditionOperator)
            return item.name;
        else
            return item;
    
};

Generic components to the rescue. By wrapping your head around the generic means of doing this, you can actually simplify and make a much more broadly useable component.

The problem here is that you are passing in a bunch of different types, but your getItem function is hard-coded. You should instead pass in the getItem function that is appropriate to the type being shown.

Say you define your props interface as follows:

interface IListProps<T> {
    handleRemove: (item: T) => void;
    itemsList: T[];
    getItem: (v: T, index: number) => string; //pass this function into your component
}

and your component as:

const List = <T,>({ handleRemove, itemsList, getItem }: React.PropsWithChildren<IListProps<T>>): JSX.Element => (
    <ListWrapper>
        <ul style={{ listStyleType: 'none', padding: '0', margin: 0 }}>
            {itemsList.map((item, index) => (
                <DivWithToolTip
                    data-tooltip={item}
                    // eslint-disable-next-line react/no-array-index-key
                    key={index}
                    style={{
                        display: 'flex',
                        justifyContent: 'row',
                        alignContent: 'center',
                        alignItems: 'center',
                    }}
                >
                    <li style={{ marginTop: '10px' }}>{getItem(item, index)}</li>
                    <PlusButton
                        style={{ marginTop: '10px' }}
                        {...{ addIcon: false }}
                        onClick={() => handleRemove(item)}
                    />
                </DivWithToolTip>
            ))}
        </ul>
    </ListWrapper>
);

Now you can use it with any kind of item and extract the right string from it using the getItem selector function.

const [items,setItems] = React.useState([{name:"Christian"}]);
return <List 
           itemsList = {items} 
           handleRemove = {(item) => setItems(items.filter(v => v != item)} 
           getItem={(v, i) => `${v.name}`} />

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