Skip to content

Instantly share code, notes, and snippets.

@zigcccc
Last active November 3, 2022 08:19
Show Gist options
  • Select an option

  • Save zigcccc/a945a47d28f6a573a731e6069cda5ef6 to your computer and use it in GitHub Desktop.

Select an option

Save zigcccc/a945a47d28f6a573a731e6069cda5ef6 to your computer and use it in GitHub Desktop.

Revisions

  1. zigcccc revised this gist Nov 3, 2022. No changes.
  2. zigcccc revised this gist Nov 3, 2022. 4 changed files with 140 additions and 1 deletion.
    2 changes: 2 additions & 0 deletions PageableTable.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // 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
    62 changes: 62 additions & 0 deletions PageableTableBody.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,62 @@
    // 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} />;
    };
    4 changes: 3 additions & 1 deletion ProductsTable.js
    Original file line number Diff line number Diff line change
    @@ -1,3 +1,5 @@
    // System / template (strictly tied to "Products" feature)

    const ProductsTable = ({ fetchProducts, products, ...rest }) => {
    const history = useHistory();

    @@ -18,4 +20,4 @@ const ProductsTable = ({ fetchProducts, products, ...rest }) => {
    {...rest}
    />
    );
    };
    };
    73 changes: 73 additions & 0 deletions Table.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,73 @@
    // 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>
    );
    };
  3. zigcccc created this gist Nov 3, 2022.
    82 changes: 82 additions & 0 deletions PageableTable.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,82 @@
    /**
    * 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>
    );
    };
    21 changes: 21 additions & 0 deletions ProductsTable.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,21 @@
    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}
    />
    );
    };