i'm looking for some guidance to what would be considered best practice when developing a custom data table.
Background: I've been tasked with created a table component that has both static and asynchronous loading for when the user paginates, sorts, and or searches. Currently I have a single table that handles both of these branches but its getting a bit bloated and would like to separate out the two branches.
So far I have two thoughts:
- Have a Table component that will display data and turn on specific features for developer (sorting, searching, paginating). Then have two higher order components that wrap the original Table component that will handle either asynchronous or static loading for when the user sorts, searches, or paginates through the Table.
- The problem I see with this implementation is these higher order components will be quite coupled to the original Table components implementation and the one (withAsyncSubscription shown below) takes in a prop that that original Table component does not to prepare the api data to be used in the Table component.
- Have a Table component that will display data and turn on specific features for developer (sorting, searching, paginating). Create a wrapping component for the original Table component that will handle re-fetching data (asynchronous data loading passing in sort, search, and pagination parameters to the back-end) and another wrapping component that wraps the original Table component that will transform the original data set (static data loading performing sort, search, and pagination on the front-end).
- The problem I see with this implementation is having to relay lots of props through this wrapping component into the original Table component.
What solution sounds better in theory and more aligned with React class based components best practices? I have already tried my first thought and it looks something like this:
export class Table extends React.Component {
static propTypes = {
headings: PropTypes.object,
data: PropTypes.arrayOf(PropTypes.object),
sortable: PropTypes.arrayOf(PropTypes.string),
searchable: PropTypes.arrayOf(PropTypes.string),
pageSize: PropTypes.number,
totalRecords: PropTypes.number,
loadData: PropTypes.func,
loading: PropTypes.bool,
}
load() {
// call external loadData prop if it exists
// or
// perform synchronous sort, search, pagination
}
renderSortableColumns() {
// render sort column headers if sortable prop exists
}
renderSearch() {
// render search if searchable prop exists
}
renderPagination() {
// render table pagiantion if pageSize prop exists
}
render() {
// render table
}
}
export function withStaticSubscription(Table) {
return class WithStaticSubscription extends React.Component {
static propTypes = {
data: PropTypes.any
}
constructor(props) {
super(props);
this.load = this.load.bind(this);
this.state = { displayData: [], totalRecords: 0 };
}
load(sort, search, currentPage, pageSize) {
// perform sort, search, and pagination with original static data set
// set data and pass it into original Table component
this.setState({ displayData, totalRecords });
}
render = () => {
const { displayData, totalRecords } = this.state;
return <Table
{...this.props}
data={displayData}
loadData={this.load}
totalRecords={totalRecords}
/>;
}
};
}
export function withAsyncSubscription(Table) {
return class WithAsyncSubscription extends React.Component {
static propTypes = {
loadData: PropTypes.func, // api to get new table data
transformData: PropTypes.func // custom transformer that prepares data for table
}
static defaultProps = {
loadData: () => {},
transformData: () => {}
}
constructor(props) {
super(props);
this.load = this.load.bind(this);
this.state = { displayData: [], totalRecords: 0 };
}
load = async(sort, search, currentPage, pageSize) => {
const { loadData, transformData } = this.props;
const searchParams = {
search: false,
page: currentPage + 1,
rows: pageSize,
sortColumn: sort.column,
sortOrder: sort.order
};
this.setState((prev) => { return { ...prev, loading: true }; });
const { data, totalRecords } = await loadData(searchParams);
const displayData = transformData(data);
this.setState({ totalRecords: totalRecords, displayData, loading: false });
}
render = () => {
const { loading, displayData, totalRecords } = this.state;
return <Table
{...this.props}
data={displayData}
loadData={this.load}
loading={loading}
totalRecords={totalRecords}
/>;
}
};
}
export const TableWithAsyncSubscription = withAsyncSubscription(Table);
export const TableWithStaticSubscription = withStaticSubscription(Table);