Is there a way for my child component to know about the parent component's prop types?
I don't want to hardcode them into the child, as the child will be used by multiple parents with slightly different props.
A simplified example:
// Parent.tsx
import React, {ReactElement} from "react";
interface Row {
id: string;
name: string;
tags: string[];
}
const Parent = (): ReactElement => {
const rows: Row[] = [
{
id: "1",
name: "Foo",
tags: ["new"],
},
{
id: "1",
name: "Bar",
tags: ["new", "sale"],
},
];
const formatRow = (row: Row): ReactElement => (
<tr key={row.id}>
<td>{row.id}</td>
<td>{row.name}</td>
<td>{row.tags.map(tag => <strong>{tag}</strong>)}</td>
</tr>
);
return <Child formatRow={formatRow} rows={rows} />;
};
export default Parent;
// Child.tsx
import React, {ReactElement} from "react";
const Child = ({formatRow, rows}): ReactElement => {
const columnCount = Object.keys(rows[0]).length;
return (
<table>
<tbody>
{rows.map(row => formatRow(row))}
</tbody>
<tfoot>
<tr>
<td colSpan={columnCount}>Footer</td>
</tr>
</tfoot>
</table>
);
};
export default Child;
There could then also be another parent, Parent2
with this shape for it's own implementation of Row
:
// Parent2.tsx
interface Row {
id: string;
count: number;
}
const Parent2 = (): ReactElement => {
// similar code to `Parent` here
};
export default Parent2;
I'm not sure how to let the Child
component know that formatRow
expects a Row
and returns a ReactElement
, and that rows is an array of Row
objects?
I think Generics might be the answer, but I have extremely limited knowledge on that, so any help whether Generics or otherwise is appreciated.
CodePudding user response:
Usually, if you want to be argument agnostic
you should use generics:
import React, { ReactElement } from "react";
interface Row {
id: string;
name: string;
tags: string[];
}
const Child = <R,>({ formatRow, rows }: { rows: R[], formatRow: (row: R) => ReactElement }): ReactElement => {
const columnCount = Object.keys(rows[0]).length;
return (
<table>
<tbody>
{rows.map(row => formatRow(row))}
</tbody>
<tfoot>
<tr>
<td colSpan={columnCount}>Footer</td>
</tr>
</tfoot>
</table>
);
};
const Parent = (): ReactElement => {
const rows: Row[] = [
{
id: "1",
name: "Foo",
tags: ["new"],
},
{
id: "1",
name: "Bar",
tags: ["new", "sale"],
},
];
const formatRow = (row: Row): ReactElement => (
<tr key={row.id}>
<td>{row.id}</td>
<td>{row.name}</td>
<td>{row.tags.map(tag => <strong>{tag}</strong>)}</td>
</tr>
);
return <Child formatRow={formatRow} rows={rows} />;
};
Here you have simplified version:
interface ChildProps<R> {
rows: R[],
formatRow: (row: R) => ReactElement
}
const Child = <R,>({ formatRow, rows }: ChildProps<R>): ReactElement => {
const columnCount = Object.keys(rows[0]).length;
return (
<table>
<tbody>
{rows.map(row => formatRow(row))}
</tbody>
<tfoot>
<tr>
<td colSpan={columnCount}>Footer</td>
</tr>
</tfoot>
</table>
);
};
So, R
is infered type of rows
property. This is how inference works. YOu provide any value to rows
and R
will represent type of the value. Then, you can use R
for other proeprties.
If you are interested in type inferece on function arguments you can check my article