Reputation: 2192
I want to switch to functional style with hooks, but still cannot figure a way out. For example I have an abstract class representing a table
import React from "react";
import ErrorBoundary from "Components/ErrorBoundary";
import { Link } from "react-router-dom";
import { Table, Divider, Alert, Popconfirm } from "antd";
export default class AbstractTable extends React.Component {
constructor( props ) {
super( props );
this.state = {
pagination: {
current: 1,
pageSize: 50,
total: 0
},
filterParams: {},
sortParams: {},
filterParams: {}
}
this.url = this.props.baseUrl;
this.table = this.constructor.displayName;
this.addButtonText = `New record`;
}
renderActions = ( text, record ) => (
<span>
<Link to={ `${ this.url }/${ record.id }` }>Edit</Link>
<Divider type="vertical" />
<Popconfirm placement="topRight" title="Are you sure to delete this record?"
onConfirm={ () => this.removeRecord( record.id ) } okText="Yes" cancelText="No">
<a href="#">Delete</a>
</Popconfirm>
</span>
)
removeRecord( id ) {
this.api.remove( id );
this.props.actions.loadTable( this.table, this.api );
}
/**
* Get query params from filters given as key/value object literal like
* { filter[firstName]: "val1", filter[lastName]: "val2" }
*/
static getFilterParams( filters ) {
return Object.entries( filters ).reduce(( carry, pair ) => {
carry[ `filter[${ pair[ 0 ] }]` ] = pair[ 1 ];
return carry;
}, {});
}
// Overridable
hookOnTableChange() {
}
/**
* Send XHR to update table content
*/
onTableChange = ( pagination, filters, sorter ) => {
const pager = { ...this.state.pagination };
// Intercept incoming pagination, filters, sorter, can map field for sort=true
this.hookOnTableChange( pagination, filters, sorter );
pager.current = pagination.current;
// Normalize filters from {firstName: [ "value1" ], lastName: [ "value2" ]} to key-value object
const filterMap = Object.entries( filters ).reduce( ( carry, pair ) => {
if ( pair[ 1 ] === null ) {
return carry;
}
carry[ pair[ 0 ] ] = pair[ 1 ].join( "," );
return carry;
}, {}),
filterParams = { ...this.state.filterParams, ...filterMap };
const sortParams = {
sortField: sorter.field,
sortOrder: sorter.order === "ascend" ? "ASC" : "DESC",
}
this.setState({
pagination: pager,
sortParams,
filterParams
});
this.fetch({
pageSize: pagination.pageSize,
current: pagination.current,
...sortParams,
...AbstractTable.getFilterParams( filterParams )
});
}
fetch = ( params = {} ) => {
this.props.actions.loadTable( this.table, this.api, params );
}
async componentDidMount() {
const { preloaded } = this.props.store.app.tables[ this.table ];
if ( preloaded ) {
return;
}
this.fetch();
}
addRecord = ( e ) => {
e.preventDefault();
}
render() {
const { rows, total, loading, errorMessage } = this.props.store.app.tables[ this.table ];
return (<ErrorBoundary>
{ errorMessage ? <Alert
message="Error"
description={ errorMessage }
type="error"
/> : null }
<Table columns={ this.state.columns }
loading={ loading }
dataSource={ rows }
onChange={ this.onTableChange }
pagination = { {
...this.state.pagination,
total
}}
/>
</ErrorBoundary>);
}
};
Specific table extends the abstract one, it reuses the shared methods. It may overrides some if necessary. E.g.
export default class SettingsProjectTable extends AbstractTable {
constructor( props ) {
super( props );
this.api = api;
this.state = {
columns: [{
title: "Name",
dataIndex: "name",
sorter: true
},
{
title: "Environment",
dataIndex: "env",
sorter: true,
filters: [
{
text: 'test',
value: 'test',
},
{
text: 'live',
value: 'live',
}
]
},
{
title: "Actions",
key: "action",
width: "120px",
render: this.renderActions
}]
}
}
};
So what can be a efficient refactoring? Should I make a component and pass specifics and alternating methods with properties?
Upvotes: 1
Views: 73
Reputation: 91555
You don't need a separate component for a "settings table". It's just a "table" component with different data passed to it via props.
It would probably be a good idea to extract the API and pagination features into a reusable "provider" component. The provider then wraps the table and yields values to its children; The table component, in your case.
<ApiProvider>
<MyTable data={data} columns={['id','name','date']}>
</ApiProvider>
The whole point of this is to make your components more generic and, therefore, more reusable. By making your table "dumb" (no direct API access), it can be reused anywhere you need a data-driven table. By making the API accessed through a provider, you can provide data to any child components, not just tables.
You can also useContext
to provide a data that's accessible at any depth in your application.
Upvotes: 1