I would like to make a general component that render a list. I am having trouble when i have to pass the key. I thought I was passing the property name which is to be taken as a key, but I get an error
import { Swiper, SwiperSlide } from "swiper/react"
interface HorizontalSectionProps<T> {
// Title of the section
title: string
// Items that render each element of the section
card: (args0: T) => React.ReactNode
// Items to show
items: T[]
// Property that is a key
keyProp: keyof T
// Loading state
isLoading?: boolean
}
/**
* This component is used to display a horizontal section with a title and a list of items
* For mobile there is an horizontal scrolling section
* For tablet there is grids of cards displayed in max 2 rows
* For desktop there is grids of cards displayed in one 1 row
* @param {HorizontalSectionProps} props
*/
export default function HorizontalSection<T>({
title,
card,
items,
keyProp,
isLoading = false,
}: HorizontalSectionProps<T>) {
return (
<>
<h2 className="text-2xl sm:text-3xl mb-6">{title}</h2>
{isLoading ? (
<>Loading</>
) : (
<>
{/* ===== MOBILE HORIZONTAL SECTION ===== */}
<Swiper
slidesPerView={2}
// centeredSlides={true}
spaceBetween={10}
loop={true}
grabCursor={true}
pagination={{
clickable: true,
type: "bullets",
}}
className="mySwiper card-swiper sm:hidden"
>
{items?.map((item, index) => (
//TODO: Here I have the error on key property
<SwiperSlide key={item[keyProp]}>{card(item)}</SwiperSlide>
))}
</Swiper>
{/* ===== DESKTOP HORIZONTAL SECTION ===== */}
<div className="hidden sm:grid grid-cols-2 sm:grid-cols-3 md:grid-cols-5 gap-x-4 gap-y-10 sm:gap-y-6 mb-10">
{items?.map((item) => card(item))}
</div>
</>
)}
</>
)
}
I get this error:
Type 'T[keyof T]' is not assignable to type 'Key | null | undefined'.
Type 'T[string] | T[number] | T[symbol]' is not assignable to type 'Key | null | undefined'.
Type 'T[string]' is not assignable to type 'Key | null | undefined'.ts(2322)
EDIT: I added the way i use the component, so you can understand better. Inside card i pass the component that needs to be rendered for each item and inside keyProp I wanted to pass the name of the property that acts as a key
<div className="recSection my-12 app-max-width app-x-padding">
<HorizontalSection<Product>
title={t("you_may_also_like")}
card={(item: Product) => <Card key={item.nodo} item={item} />}
items={suggestedProducts}
keyProp="nodo"
/>
</div>
CodePudding user response:
Problem
You didn't include the example props, but let say that T
is a string
Now keyof T
is something like: keyProp: number | typeof Symbol.iterator | "toString" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | ... 30 more ... | "padEnd"
Solution
You probably meant:
keyof T[];
CodePudding user response:
In the cases like this, you need to have an abstraction based on your key:
interface HorizontalSectionProps<K extends string, T extends Record<K, string | number>> {
// Title of the section
title: string
// Items that render each element of the section
card: (args0: T) => ReactNode
// Items to show
items: T[]
// Property that is a key
keyProp: keyof T
// Loading state
isLoading?: boolean
}
Then you'll need to change your component to look like this:
export default function HorizontalSection<K extends string, T extends Record<K, string | number>>({
title,
card,
items,
keyProp,
isLoading = false,
}: HorizontalSectionProps<K, T>) {
Then it'll assume that T[K]
is a string or a number.
See the example here