I'm guessing a bit about your intentions, but I am interpreting this as:
- You want to support rendering different kinds of cards for different
T types.
- You want to pass through certain props to every individual card.
I came up with a solution based on a render props pattern. We now have two generics -- T which is still the item type and Extra which is the props that we pass through.
Each individual card will receive the properties of the item as props, all of the Extra props, and set and setSet (not sure if those last two are actually needed).
The card group requires set, setSet, the Extra props, and a Render component for the individual card.
import React, { Dispatch, SetStateAction, ComponentType, useState } from "react";
interface SetProps<T> {
data: T[];
}
interface CardProps<T> {
set: SetProps<T>;
setSet: Dispatch<SetStateAction<SetProps<T>>>;
}
type CardGroupProps<T, Extra = {}> = Extra &
CardProps<T> & {
Render: ComponentType<T & Extra & CardProps<T>>;
};
const CardGroup = <T extends object, Extra extends object>(
{set, setSet, Render, ...extra}: CardGroupProps<T, Extra>
) => (
<>
{set.data.map((item: T, idx: number) => (
<SwipeBox
key={idx}
state={{ set, setSet }}
>
<Render
set={set}
setSet={setSet}
{...(extra as Extra)}
{...item}
/>
</SwipeBox>
))}
</>
);
Here's a silly example to show usage. Here our RenderMovieCard component requires the Movie item and also a color prop. You will get an error if you try to call a CardGroup with Render={RenderMovieCard} without passing this required color prop.
interface Movie {
title: string;
}
const RenderMovieCard = ({ title, color }: Movie & { color: string }) => (
<div style={{ color }}>{title}</div>
);
export default () => {
const [movies, setMovies] = useState<SetProps<Movie>>({
data: [{ title: "Jaws" }, { title: "Star Wars" }]
});
return (
<CardGroup
set={movies}
setSet={setMovies}
Render={RenderMovieCard}
color="red"
/>
);
};
Code Sandbox