In a class based component, I can easily write some code like this:
import * as React from 'react';
import { render } from 'react-dom';
interface IProps<T> {
collapsed: boolean;
listOfData: T[];
displayData: (data: T, index: number) => React.ReactNode;
}
class CollapsableDataList<T> extends React.Component<IProps<T>> {
render () {
if (!this.props.collapsed) {
return <span>total: {this.props.listOfData.length}</span>
} else {
return (
<>
{
this.props.listOfData.map(this.props.displayData)
}
</>
)
}
}
}
render(
<CollapsableDataList
collapsed={false}
listOfData={[{a: 1, b: 2}, {a: 3, b: 4}]}
displayData={(data, index) => (<span key={index}>{data.a + data.b}</span>)}
/>,
document.getElementById('root'),
)
Actually this CollapsableDataList component should be a functional component because it's stateless, but I can't figure out how to write a function component and use generics in props, any advise for me?
You can't create a functional component with a type annotation and make it generic. So this will NOT work as T is not defined and you can't define it on the variable level:
const CollapsableDataList : React.FunctionComponent<IProps<T>> = p => { /*...*/ }
You can however skip the type annotation, and make the function generic and type props explicitly.
import * as React from 'react';
import { render } from 'react-dom';
interface IProps<T> {
collapsed: boolean;
listOfData: T[];
displayData: (data: T, index: number) => React.ReactNode;
}
const CollapsableDataList = <T extends object>(props: IProps<T> & { children?: ReactNode }) => {
if (!props.collapsed) {
return <span>total: {props.listOfData.length}</span>
} else {
return (
<>
{
props.listOfData.map(props.displayData)
}
</>
)
}
}
render(
<CollapsableDataList
collapsed={false}
listOfData={[{a: 1, b: 2}, {a: 3, c: 4}]}
displayData={(data, index) => (<span key={index}>{data.a + (data.b || 0)}</span>)}
/>,
document.getElementById('root'),
)
The type React.FC is essentially this:
<P = {}>(props: PropsWithChildren<P>, context?: any) => ReactElement | null
so instead of this (which isn't allowed):
const Example: React.FC<Props<P>> = (props) => {
// return a React element or null
}
you can use this:
const Example = <P extends unknown>(props: PropsWithChildren<Props<P>>): ReactElement | null => {
// return a React element or null
}
For example:
const Example = <P extends unknown>({ value }: PropsWithChildren<{ value: P }>): ReactElement | null => {
return <pre>{JSON.stringify(value)}</pre>
}
Or, more strictly, if the component doesn't use the children prop and won't return null:
const Example = <P>({ value }: { value: P }): ReactElement => {
return <pre>{value}</pre>
}
then use the typed component as <Example<string> value="foo"/>
type Props<T> = {
active: T;
list: T[];
onChange: (tab: T) => void;
};
export const Tabs = <T,>({ active, list, onChange }: Props<T>): JSX.Element => {
return (
<>
{list.map((tab) => (
<Button onClick={() => onChange(tab)} active={tab === active}>
{tab}
</Button>
))}
</>
);
};