Reputation: 1854
I'm trying to type a property of a React component using generics. The problem is when I try to render the component as a render prop through another component, it seems to no longer be able to "infer" the correct type - instead it defaults to unknown and I have to use "as any" to make things work.
I've put together a working example here. Note the comments and "as any"'s: https://stackblitz.com/edit/react-ts-4oyi3d?file=index.tsx
Here's the code:
type TabProps<T> = {
data?: T;
};
type TabsProps<T> = {
children: ({
childrenArr
}: {
childrenArr: React.ReactElement<TabProps<T>>[];
}) => React.ReactElement<TabProps<T>>[];
items: React.ReactElement<TabProps<T>>[];
};
type Data = { hello: string };
const Tab = <T extends unknown>({ data }: TabProps<T>) => <div>...</div>;
const Tabs = <T extends unknown>({ children, items }: TabsProps<T>) => {
const childrenArr = useMemo(() => (Array.isArray(items) ? items : [items]), [
items
]);
// ...
// In real application this is where manipulation of the elements in the childrenArr would take place
// Since this is just an example I'll just do this
const manipulatedchildrenArr = childrenArr.map(child => {
return {
...(child as any),
props: { data: { hello: "world is manipulated" } }
};
});
return (
<div>
<div>Hello</div>
<div>
{typeof children === "function"
? children({ childrenArr: manipulatedchildrenArr })
: children}
</div>
</div>
);
};
const App = () => {
const data: Data = { hello: "World" };
return (
<div className="App">
<Tabs items={[<Tab data={data} />]}>
{({ childrenArr }) =>
childrenArr.map(child => (
// Remove "as any" and it will be type unknown and result in an error
<div>{(child.props as any).data.hello}</div>
))
}
</Tabs>
</div>
);
};
As you can see the type of the data
prop is lost.
Upvotes: 0
Views: 2321
Reputation: 330
Now I'm not sure if I went outside the scope of what you were looking for and If I did please let me know and I'll adjust the solution..
Update: I forgot to add code for Single tab.
import React from "react";
import ReactDOM from "react-dom";
export interface ITabProps<T> {
data?: T;
handleProcessData: (data: T) => string;
}
export function Tab<T>(props: ITabProps<T>) {
const data = props.data ? props.handleProcessData(props.data) : "None";
return <div>Hello {data}</div>;
}
export type TabElement<T> = React.ReactElement<ITabProps<T>> | React.ReactElement<ITabProps<T>>[]
export interface ITabsProps<T> {
handleManipulation: (data: T) => T;
children: TabElement<T>
}
export function Tabs<T>(props: ITabsProps<T>) {
const array = [] as TabElement<T>[];
if (Array.isArray(props.children))
props.children.forEach((child) => {
let mChild = <Tab<T> handleProcessData={child.props.handleProcessData} data={props.handleManipulation(child.props.data)} /> as TabElement<T>;
array.push(mChild)
})
else {
let mChild = <Tab<T> handleProcessData={props.children.props.handleProcessData} data={props.handleManipulation(props.children.props.data)} /> as TabElement<T>;
array.push(mChild)
}
return <div>{array.map((item) => (item))}</div>;
}
export type Data = { hello: string };
export function App() {
//B.C. it's generic you going to have to have some form of generic control functions
const handleProcessData = (data: Data) => {
//Here you have to specifiy how this specific data type is processed
return data.hello;
};
const handleManipulation = (data: Data) => {
//here you would have all your manipulation logic
return { hello: data.hello + " is manipulated" };
}
//To Make this easier to use you could nest handleProcessData inside the Tabs component
return (
<div>
<Tabs<Data> handleManipulation={handleManipulation}>
<Tab<Data> handleProcessData={handleProcessData} data={{hello: "world1"}} />
<Tab<Data> handleProcessData={handleProcessData} data={{hello: "world2"}} />
</Tabs>
</div>
);
}
ReactDOM.render(<App />, document.getElementById("root"));
Upvotes: 1