Reputation: 49
it's been 2 days i'm struggling to make a reusable table but i have problems with types.
I'm not a typescript/react expert because i worked more with javascript so i would like you to show me the right way to develop this component.
Info:
typescript: "3.6.4"
react: "^16.11.0"
"engines": {
"node": "10.x",
"npm": "6.x"
}
My idea is (uncomplete and with types errors):
import * as React from 'react';
const getCellData = (fields: Record<string, string>, row) => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return Object.entries(fields).reduce((res: Record<string, any>, val) => {
res[val[0]] = row[val[1]];
return res;
}, {});
};
interface Props<C, R> {
columns: C[];
rows: R[];
}
const Table = <C, R>({ columns, rows }: Props<C, R>) => {
return (
<div className="table-responsive">
<table className="table table-striped">
<thead>
<tr>
{columns.map((column, i: number) => (
<th scope="col" key={i}>
{column.label}
</th>
))}
</tr>
</thead>
<tbody>
{rows &&
rows.map((row, j: number) => {
return (
<tr key={j}>
{columns.map((column, k: number) => {
const { Template, fields } = column;
const data = getCellData(fields, row);
return <td key={k}>{<Template {...data}></Template>}</td>;
})}
</tr>
);
})}
</tbody>
</table>
</div>
);
};
export default Table;
I'm using the table like this in JSX (Is it right to pass types in jsx like this?):
<Table<SportsTableColumn, RedaxSport> columns={columns} rows={sports}></Table>
where SportsTableColumn is:
export interface SportsTableColumn {
label: string;
Template: typeof TableTdText | typeof TableTdTextWithImage | typeof TableTdBoolCheck;
fields: {
text?: string;
image?: string;
checked?: string;
};
}
TableTdText, TableTdTextWithImage, TableTdBoolCheck are React.FC
Then we have the RedaxSport that is a class with properties like:
this.sportId = sportId;
this.urlIcon = urlIcon;
this.redaxDescription = redaxDescription;
this.aamsDescription = aamsDescription;
this.isDailyProgram = isDailyProgram;
"columns" array data example:
[
{
label: intl.formatMessage({ id: 'sports.table.redaxDescription' }),
Template: TableTdTextWithImage,
fields: {
text: 'redaxDescription',
image: 'urlIcon'
}
},
{
label: intl.formatMessage({ id: 'sports.table.aamsDescription' }),
Template: TableTdText,
fields: {
text: 'aamsDescription'
}
},
{
label: intl.formatMessage({ id: 'sports.table.dailyProgram' }),
Template: TableTdBoolCheck,
fields: {
checked: 'isDailyProgram'
}
}
];
"rows" data example:
[
{
"sportId": 0,
"urlIcon": "https://url.com/",
"redaxDescription": "This is a description",
"aamsDescription": "Another description",
"isDailyProgram": true
},
{
"sportId": 1,
"urlIcon": "https://url2.com/",
"redaxDescription": "This is a description 2",
"aamsDescription": "Another description 2",
"isDailyProgram": false
}
]
I just hope to have been clear about my intent and gave you all the info you need. Thank you
Upvotes: 2
Views: 8364
Reputation: 21
Have a look at a generic table for example
It can be reused and does not requires a lot of code
Example of using the generic table:
type MyElement = {
firstName: string;
lastName: string;
age: number;
};
const elements: MyElement[] = [
{ firstName: 'A', lastName: 'A', age: 10 },
{ firstName: 'B', lastName: 'B', age: 11 },
{ firstName: 'C', lastName: 'C', age: 12 },
{ firstName: 'D', lastName: 'D', age: 13 },
{ firstName: 'E', lastName: 'E', age: 14 },
];
const model: TableModel<MyElement> = {
columns: [
{
title: 'Name',
html: e => <span>{e.firstName} {e.lastName}</span>
},
{
title: 'Firstname',
html: e => <span>{e.firstName}</span>
},
{
title: 'Lastname',
html: e => <span>{e.lastName}</span>
},
{
title: 'Age',
html: e => <span>{e.age}</span>
},
]
};
export const Table = () => {
return (
<div className={styles.tableContainer}>
<GenericTable model={model} elements={elements}></GenericTable>
</div>
);
};
Upvotes: 2
Reputation: 49
i replaced Template: React.ComponentClass;
with Template: typeof TableTdText | typeof TableTdTextWithImage | typeof TableTdBoolCheck;
but i'm getting
Type 'SportsTableColumn' does not satisfy the constraint 'Column'.
Types of property 'Template' are incompatible.
Type 'FunctionComponent<OwnProps> | FunctionComponent<OwnProps> | FunctionComponent<OwnProps>' is not assignable to type 'ComponentClass<{}, any>'.
Type 'FunctionComponent<OwnProps>' is not assignable to type 'ComponentClass<{}, any>'.
Type 'FunctionComponent<OwnProps>' provides no match for the signature 'new (props: {}, context?: any): Component<{}, any, any>'.
Thank you for helping me!
Upvotes: 0
Reputation: 844
U have to give your columns a type or an interface like:
interface IColumn {
label: string;
fields: { [key: string]: string };
Template: React.ComponentClass;
};
And say to the C generic in your table component that it accepts everything that extends this interface
const Table = <C extends IColumn, R>({ columns, rows }: Props<C, R>) => {...}
So this will let your component know it can use any field from the IColumn
interface.
and then extend your SportsTableColumn
with IColumn
as well
export interface SportsTableColumn extends IColumn {
label: string;
Template: React.ComponentClass;
fields: {
text?: string;
image?: string;
checked?: string;
};
}
Note: Replace React.ComponentClass
to your TdComponents
Upvotes: 1