Last active
November 3, 2022 08:19
-
-
Save zigcccc/a945a47d28f6a573a731e6069cda5ef6 to your computer and use it in GitHub Desktop.
Generic Pageable Table
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Organism (utilizes Table component which is a molecule, but is not strictly tied to a single feature) | |
/** | |
* The goal of this component is to: | |
* - render the table data | |
* - handle initial API call + any additional page and/or search related API calls | |
* - render table header & footer with pagination and actions | |
*/ | |
const PageableTable = ({ | |
columns, | |
components, | |
data, | |
filters, | |
hasError, | |
isLoading, | |
onFetchData, | |
onRowClick, | |
pagination, | |
tableProps: tablePropsBase, | |
title, | |
}) => { | |
const { values: params } = useFormState(); | |
// Joined Table props | |
const tableProps = { ...tablePropsBase, columns, data, onRowClick }; | |
const emptyResults = hasEmptyResults({ data, isLoading }); | |
const emptySearchResults = hasEmptySearchResults({ data, isLoading, params }); | |
// We are setting isSearching to true if we are in a loading state and the value | |
// for the search Query exists | |
const isSearching = isLoading && Boolean(params.searchKey); | |
// Props shared between all of the PageableTable child components | |
const commonProps = { isLoading, isSearching }; | |
const handleRefresh = () => { | |
onFetchData({ page: pagination.currentPage, params }); | |
}; | |
const handleFetchPreviousPage = () => { | |
if (pagination.hasPreviousPage) { | |
onFetchData({ page: pagination.currentPage - 1, params }); | |
} | |
}; | |
const handleFetchNextPage = () => { | |
if (pagination.hasNextPage) { | |
onFetchData({ page: pagination.currentPage + 1, params }); | |
} | |
}; | |
return ( | |
<div className="pageable-table"> | |
<TableActionbar | |
{...commonProps} | |
filters={filters} | |
hasError={hasError} | |
onGoToNextPage={handleFetchNextPage} | |
onGoToPreviousPage={handleFetchPreviousPage} | |
onRefresh={handleRefresh} | |
pagination={pagination} | |
title={title} | |
/> | |
<PageableTableBody | |
{...commonProps} | |
{...tableProps} | |
components={components} | |
hasEmptyResults={emptyResults} | |
hasEmptySearchResults={emptySearchResults} | |
hasError={hasError} | |
onRefresh={handleRefresh} | |
params={params} | |
/> | |
<TableFooter | |
{...commonProps} | |
hasError={hasError} | |
onGoToNextPage={handleFetchNextPage} | |
onGoToPreviousPage={handleFetchPreviousPage} | |
pagination={pagination} | |
/> | |
</div> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Sub-component of the PageableTable organism component | |
const PageableTableBody = ({ | |
components, | |
hasEmptyResults, | |
hasEmptySearchResults, | |
hasError, | |
isLoading, | |
isSearching, | |
onRefresh, | |
params, | |
...rest | |
}) => { | |
// Components setup for different Table empty states. We use either component provided by the prop or the | |
// default one. We also memoize this component, since it will (almost) never change dynamically. | |
const TableLoadingState = React.useMemo(() => components?.LoadingState || DefaultTableLoadingState, [components]); | |
const TableErrorState = React.useMemo(() => components?.ErrorState || DefaultTableErrorState, [components]); | |
const TableEmptyResults = React.useMemo(() => components?.EmptyResults || DefaultTableEmptyResults, [components]); | |
/** | |
* The top-most check. Whenever we are in a loading or searching state, we want to display | |
* the table loading state component. | |
*/ | |
if (isLoading || isSearching) { | |
return ( | |
<TableBodyEmptyStateBase> | |
<TableLoadingState /> | |
</TableBodyEmptyStateBase> | |
); | |
} | |
/** | |
* If we have an error (and we are currently not loading anything), we display the table | |
* error state component. | |
*/ | |
if (hasError) { | |
return ( | |
<TableBodyEmptyStateBase> | |
<TableErrorState onRefresh={onRefresh} /> | |
</TableBodyEmptyStateBase> | |
); | |
} | |
/** | |
* If we have empty search results, we want to display empty search results | |
* state. IMPORTANT: This check must happen before the generic empty results check | |
* since by design, empty search results also means empty results, but not vice versa. | |
*/ | |
if (hasEmptySearchResults || hasEmptyResults) { | |
return ( | |
<TableBodyEmptyStateBase> | |
<TableEmptyResults onRefresh={onRefresh} searchKey={params?.searchKey} /> | |
</TableBodyEmptyStateBase> | |
); | |
} | |
/** | |
* If none of the empty state conditions are met, we return | |
* the actual table component | |
*/ | |
return <Table {...rest} />; | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// System / template (strictly tied to "Products" feature) | |
const ProductsTable = ({ fetchProducts, products, ...rest }) => { | |
const history = useHistory(); | |
const handleEditProductClick = (row) => { | |
history.push(getJoinedPath('products', row.original.id)); | |
}; | |
const columns = getProductsTableColumns({ onEditProduct: handleEditProductClick }); | |
return ( | |
<PageableTable | |
columns={columns} | |
components={{ ErrorState: ProductsTableErrorState, EmptyResults: ProductsTableEmptyState }} | |
data={products} | |
onFetchData={fetchProducts} | |
onRowClick={handleEditProductClick} | |
title="Seznam izdelkov" | |
{...rest} | |
/> | |
); | |
}; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Molecule (since it utilizes Text component, which is an atom) | |
const Table = ({ cellPaddingSize, columns, data, hideHeader, onRowClick }) => { | |
// Data passed to the useTable hook needs to be memoized | |
// to avaid performance leaks | |
const memoColumns = React.useMemo(() => columns, [columns]); | |
const memoData = React.useMemo(() => data, [data]); | |
const numOfColumns = React.useMemo(() => memoColumns.length, [memoColumns]); | |
const defaultTableCellSize = getDefaultTableCellSize(numOfColumns); | |
const handleRowClick = (row) => { | |
onRowClick?.(row); | |
}; | |
const { getTableProps, getTableBodyProps, headerGroups, rows, prepareRow } = useTable({ | |
columns: memoColumns, | |
data: memoData, | |
}); | |
return ( | |
<div className="table"> | |
<table className="table__table" {...getTableProps()}> | |
{!hideHeader && ( | |
<thead className="table__head"> | |
{headerGroups.map((headerGroup) => ( | |
// Apply the header row props | |
<tr className="table__row" {...headerGroup.getHeaderGroupProps()}> | |
{headerGroup.headers.map((column) => ( | |
<th | |
className={classNames('table__cell', cellPaddingSize, column.size || defaultTableCellSize, { | |
[column.align]: Boolean(column.align), | |
[`${column.alignHeader}--important`]: Boolean(column.alignHeader), | |
})} | |
{...column.getHeaderProps()} | |
> | |
<Text.Regular numOfLines={1}>{column.render('Header')}</Text.Regular> | |
</th> | |
))} | |
</tr> | |
))} | |
</thead> | |
)} | |
<tbody className="table__body" {...getTableBodyProps()}> | |
{rows.map((row) => { | |
// Prepare the row for display | |
prepareRow(row); | |
return ( | |
// Apply the row props | |
<tr | |
className={classNames('table__row table__row--body', { 'table__row--clickable': Boolean(onRowClick) })} | |
onClick={() => handleRowClick(row)} | |
{...row.getRowProps()} | |
> | |
{row.cells.map((cell) => ( | |
<td | |
className={classNames('table__cell', cellPaddingSize, cell.column.size || defaultTableCellSize, { | |
[cell.column.align]: Boolean(cell.column.align), | |
})} | |
{...cell.getCellProps()} | |
> | |
{cell.render('Cell')} | |
</td> | |
))} | |
</tr> | |
); | |
})} | |
</tbody> | |
</table> | |
</div> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment