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