I am currently creating a Compound Component type component. At that time I am looking for a way to propagate generics when using render prop.
interface ListContextValue<T> {
rows: T[];
}
const createListContext = once(<T,>() =>
createContext<ListContextValue<T> | undefined>(undefined)
);
const useListContext = <T,>() => {
const context = useContext(createListContext<T>());
if (!context) {
throw new Error(
"useListContext must be used within an ListContext.Provider"
);
}
return context;
};
interface ListProps<T> {
rows: T[];
children?: React.ReactNode;
}
export const List = <T,>({ rows, children }: ListProps<T>) => {
const ListContext = createListContext<T>();
return (
<ListContext.Provider value={{ rows }}>
{children}
</ListContext.Provider>
);
};
export interface ListContainerProps<T> extends Omit<SlotProps, "children"> {
children?: (props: { row: T }) => React.ReactNode;
}
export const ListContainer = <T,>({ children }: ListContainerProps<T>) => {
const { rows } = useListContext<T>();
return (
<div>
{rows.map((row) => {
return children ? children({ row }) : null;
})}
</div>
);
};
Above is the List component code.
export default function UserList() {
return (
<List rows={rows}>
<ListContainer>
{({ row }) => (
<div>
{/** row is unknown type */}
{row}
</div>
)}
</ListContainer>
</List>
);
}
When using the component, is it possible to automatically infer the row type in the render prop function?
export default function UserList() {
return (
<List<User[]> rows={rows}>
<ListContainer<User>>
{({ row }) => (
<div>
{row}
</div>
)}
</ListContainer>
</List>
);
}
Of course, it can be solved this way, but I don't think it's a good way.
Unfortunately, TypeScript has no way to detect React Context: TS is based on the Function Component signature, i.e. its props; but it knows nothing about any Context used inside.
In your very example, we can obviously get rid of the Context and directly map on the array items:
export default function UserList() {
return (
<>
{rows?.map((row) => (
// ^? (parameter) row: User
<div>
{row}
</div>
))}
</>
);
}
However, it is very probable that your example is oversimplified: you probably want to define your <ListContainer>
Component separately, hence you resort to React Context.
In that case, the easiest solution is indeed to explicitly specify the type parameter as you did (<ListContainer<User>>
).