Tables
Overview
The Tables library provides a comprehensive set of data table components built on top of React Table and Chakra UI. These components offer different table variants for various use cases, from basic data display to complex interactive tables with selection, expansion, and pagination.
Table Components
Basic Tables
- WPBasicDataTable - Simple data table with pagination
- WPGlobalBasicDataTable - Basic table with global search
- RemoteWPBasicDataTable - Basic table for remote data
Interactive Tables
- WPSelectDataTable - Table with row selection capabilities
- WPExpandableDataTable - Table with expandable rows
- WPSubComponentTable - Table with sub-component rendering
- WPActionableTable - Table with inline editing capabilities
Editable Tables
- WPBasicEditableTable - Basic table with inline editing
- ActionableTable - Advanced editable table with actions
Modern Tables (V2)
- V2WPBaseTable - Modern table using TanStack Table
- V2WPSelectDataTable - Modern selectable table
Utility Components
- TableError - Error state component
- TablePagination - Pagination component
- TableSkeletons - Loading state components
- Checkbox - Table checkbox component
- GlobalFilter - Global search component
When to Use
WPBasicDataTable
- Use for simple data display without interactions
- When you need basic pagination
- For read-only data tables
WPSelectDataTable
- Use when you need row selection functionality
- For bulk operations on table data
- When implementing data management interfaces
WPExpandableDataTable
- Use when rows contain additional detailed information
- For hierarchical or nested data display
- When you need to show/hide additional content per row
WPSubComponentTable
- Use when you need to render custom components within rows
- For complex data visualization within table cells
- When standard table cells aren't sufficient
WPActionableTable
- Use for inline editing capabilities
- When you need to modify data directly in the table
- For data entry forms within tables
V2WPBaseTable
- Use for new implementations with modern React Table
- When you need advanced table features
- For better performance with large datasets
Common Props
Standard Table Props
All table components share these common props:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| customColumn | Array | ✓ | - | Table column configuration |
| tableData | Array | ✓ | - | Data to display in table |
| isLoading | boolean | - | false | Loading state |
| error | Object | - | - | Error object |
| noDataMsg | string | - | 'No data available' | Message when no data |
| paginatedData | Object | - | - | Pagination metadata |
| initialState | Object | - | - | Initial table state |
| hiddenColumns | Array | - | - | Columns to hide |
Selection Props
For selectable tables:
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| isSelectable | boolean | - | false | Enable row selection |
| onRowClick | Function | - | - | Row click handler |
| selectedRows | Array | - | - | Currently selected rows |
Pagination Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| noFooter | boolean | - | false | Hide pagination footer |
| defaultFooter | boolean | - | false | Use default footer |
| recordsPerPage | number | - | 10 | Records per page |
API Documentation
WPBasicDataTable
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| customColumn | Array | ✓ | - | Column definitions |
| tableData | Array | ✓ | - | Table data |
| isLoading | boolean | - | false | Loading state |
| error | Object | - | - | Error object |
| noDataMsg | string | - | 'No data available' | No data message |
| paginatedData | Object | - | - | Pagination data |
| noFooter | boolean | - | false | Hide footer |
| hiddenColumns | Array | - | - | Hidden columns |
| globalSearchValue | string | - | - | Global search value |
| initialState | Object | - | - | Initial table state |
Usage Example
import WPBasicDataTable from 'components/Tables/WPBasicDataTable';
const columns = [
{
Header: 'Name',
accessor: 'name',
},
{
Header: 'Email',
accessor: 'email',
},
{
Header: 'Role',
accessor: 'role',
},
];
const data = [
{ name: 'John Doe', email: 'john@example.com', role: 'Admin' },
{ name: 'Jane Smith', email: 'jane@example.com', role: 'User' },
];
<WPBasicDataTable
customColumn={columns}
tableData={data}
isLoading={false}
noDataMsg='No users found'
/>;
WPSelectDataTable
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| customColumn | Array | ✓ | - | Column definitions |
| tableData | Array | ✓ | - | Table data |
| isLoading | boolean | - | false | Loading state |
| error | Object | - | - | Error object |
| noDataMsg | string | - | 'No data available' | No data message |
| paginatedData | Object | - | - | Pagination data |
| onRowClick | Function | - | - | Row click handler |
| initialState | Object | - | - | Initial table state |
Usage Example
import WPSelectDataTable from 'components/Tables/WPSelectDataTable';
const handleRowSelection = selectedRows => {
console.log('Selected rows:', selectedRows);
};
<WPSelectDataTable
customColumn={columns}
tableData={data}
isLoading={false}
onRowClick={handleRowSelection}
noDataMsg='No data to select'
/>;
WPExpandableDataTable
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| customColumn | Array | ✓ | - | Column definitions |
| tableData | Array | ✓ | - | Table data |
| isLoading | boolean | - | false | Loading state |
| error | Object | - | - | Error object |
| noDataMsg | string | - | 'No data available' | No data message |
| paginatedData | Object | - | - | Pagination data |
| renderRowSubComponent | Function | - | - | Sub-component renderer |
Usage Example
import WPExpandableDataTable from 'components/Tables/WPExpandableDataTable';
const renderSubComponent = ({ row }) => (
<div>
<h4>Details for {row.original.name}</h4>
<p>Additional information...</p>
</div>
);
<WPExpandableDataTable
customColumn={columns}
tableData={data}
isLoading={false}
renderRowSubComponent={renderSubComponent}
/>;
WPSubComponentTable
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| customColumn | Array | ✓ | - | Column definitions |
| tableData | Array | ✓ | - | Table data |
| isLoading | boolean | - | false | Loading state |
| error | Object | - | - | Error object |
| noDataMsg | string | - | 'No data available' | No data message |
| paginatedData | Object | - | - | Pagination data |
| renderRowSubComponent | Function | ✓ | - | Sub-component renderer |
| isBasic | boolean | - | false | Basic mode without selection |
| noFooter | boolean | - | false | Hide footer |
Usage Example
import WPSubComponentTable from 'components/Tables/WPSubComponentTable';
const renderRowSubComponent = ({ row }) => (
<Box p={4} bg='gray.50'>
<VStack align='start'>
<Text>
<strong>ID:</strong> {row.original.id}
</Text>
<Text>
<strong>Created:</strong> {row.original.created_at}
</Text>
<Text>
<strong>Updated:</strong> {row.original.updated_at}
</Text>
</VStack>
</Box>
);
<WPSubComponentTable
customColumn={columns}
tableData={data}
isLoading={false}
renderRowSubComponent={renderRowSubComponent}
isBasic={false}
/>;
V2WPBaseTable
Props
| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
| columns | Array | ✓ | - | TanStack Table columns |
| tableData | Array | ✓ | - | Table data |
| isLoading | boolean | - | false | Loading state |
| error | Object | - | - | Error object |
| noDataMsg | string | - | 'No data available' | No data message |
| isSelectable | boolean | - | false | Enable selection |
| pageMeta | Object | - | - | Pagination metadata |
| enableColumnPinning | boolean | - | false | Enable column pinning |
| enableColumnResizing | boolean | - | false | Enable column resizing |
| variant | string | - | 'base' | Table variant |
Usage Example
import { V2WPBaseTable } from 'components/Tables/V2WPTables';
const columns = [
{
accessorKey: 'name',
header: 'Name',
cell: info => info.getValue(),
},
{
accessorKey: 'email',
header: 'Email',
cell: info => info.getValue(),
},
];
<V2WPBaseTable
columns={columns}
tableData={data}
isLoading={false}
isSelectable={true}
enableColumnPinning={true}
variant='striped'
/>;
Column Configuration
Basic Column Structure
const columns = [
{
Header: 'Column Title',
accessor: 'dataKey', // key in data object
Cell: ({ value, row }) => <span>{value}</span>, // custom cell renderer
width: 200, // column width
minWidth: 100, // minimum width
maxWidth: 300, // maximum width
canSort: true, // enable sorting
canFilter: true, // enable filtering
canGroupBy: true, // enable grouping
canHide: true, // can be hidden
sticky: 'left', // sticky positioning
},
];
Custom Cell Renderers
const columns = [
{
Header: 'Status',
accessor: 'status',
Cell: ({ value }) => (
<Badge colorScheme={value === 'active' ? 'green' : 'red'}>{value}</Badge>
),
},
{
Header: 'Actions',
accessor: 'id',
Cell: ({ value, row }) => (
<HStack>
<IconButton
icon={<EditIcon />}
onClick={() => handleEdit(row.original)}
/>
<IconButton icon={<DeleteIcon />} onClick={() => handleDelete(value)} />
</HStack>
),
},
];
Grouped Columns
const columns = [
{
Header: 'Personal Info',
columns: [
{
Header: 'First Name',
accessor: 'firstName',
},
{
Header: 'Last Name',
accessor: 'lastName',
},
],
},
{
Header: 'Contact Info',
columns: [
{
Header: 'Email',
accessor: 'email',
},
{
Header: 'Phone',
accessor: 'phone',
},
],
},
];
Table Patterns
Basic Data Table
function UserTable() {
const { data, isLoading, error } = useQuery('users', fetchUsers);
const columns = useMemo(
() => [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Email', accessor: 'email' },
{ Header: 'Role', accessor: 'role' },
],
[],
);
return (
<WPBasicDataTable
customColumn={columns}
tableData={data || []}
isLoading={isLoading}
error={error}
noDataMsg='No users found'
/>
);
}
Selectable Table with Actions
function SelectableUserTable() {
const [selectedRows, setSelectedRows] = useState([]);
const { data, isLoading, error } = useQuery('users', fetchUsers);
const handleBulkDelete = () => {
// Handle bulk delete of selected rows
selectedRows.forEach(row => deleteUser(row.id));
};
const columns = useMemo(
() => [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Email', accessor: 'email' },
{ Header: 'Role', accessor: 'role' },
{
Header: 'Actions',
accessor: 'id',
Cell: ({ value, row }) => (
<Menu>
<MenuButton as={IconButton} icon={<MoreVertical />} />
<MenuList>
<MenuItem onClick={() => editUser(row.original)}>Edit</MenuItem>
<MenuItem onClick={() => deleteUser(value)}>Delete</MenuItem>
</MenuList>
</Menu>
),
},
],
[],
);
return (
<Box>
<HStack mb={4}>
<Button
onClick={handleBulkDelete}
isDisabled={selectedRows.length === 0}
>
Delete Selected ({selectedRows.length})
</Button>
</HStack>
<WPSelectDataTable
customColumn={columns}
tableData={data || []}
isLoading={isLoading}
error={error}
onRowClick={setSelectedRows}
/>
</Box>
);
}
Expandable Table with Details
function ExpandableOrderTable() {
const { data, isLoading, error } = useQuery('orders', fetchOrders);
const renderRowSubComponent = ({ row }) => (
<Box p={4} bg='gray.50'>
<VStack align='start' spacing={2}>
<Text>
<strong>Order ID:</strong> {row.original.id}
</Text>
<Text>
<strong>Customer:</strong> {row.original.customer}
</Text>
<Text>
<strong>Items:</strong>
</Text>
<List>
{row.original.items.map(item => (
<ListItem key={item.id}>
{item.name} - ${item.price}
</ListItem>
))}
</List>
</VStack>
</Box>
);
const columns = useMemo(
() => [
{ Header: 'Order #', accessor: 'orderNumber' },
{ Header: 'Date', accessor: 'date' },
{ Header: 'Total', accessor: 'total' },
{ Header: 'Status', accessor: 'status' },
],
[],
);
return (
<WPExpandableDataTable
customColumn={columns}
tableData={data || []}
isLoading={isLoading}
error={error}
renderRowSubComponent={renderRowSubComponent}
/>
);
}
Editable Table
function EditableProductTable() {
const [data, setData] = useState(initialData);
const [skipPageReset, setSkipPageReset] = useState(false);
const updateData = (rowIndex, columnId, value) => {
setSkipPageReset(true);
setData(oldData =>
oldData.map((row, index) => {
if (index === rowIndex) {
return { ...row, [columnId]: value };
}
return row;
}),
);
};
const columns = useMemo(
() => [
{
Header: 'Product Name',
accessor: 'name',
Cell: ({ value, row, column }) => (
<EditableCell
value={value}
row={row}
column={column}
updateData={updateData}
/>
),
},
{
Header: 'Price',
accessor: 'price',
Cell: ({ value, row, column }) => (
<EditableCell
value={value}
row={row}
column={column}
updateData={updateData}
type='number'
/>
),
},
],
[],
);
return (
<WPBasicEditableTable
customColumn={columns}
tableData={data}
updateData={updateData}
skipPageReset={skipPageReset}
/>
);
}
Table with Pagination
function PaginatedTable() {
const [pageIndex, setPageIndex] = useState(0);
const [pageSize, setPageSize] = useState(10);
const { data, isLoading, error } = useQuery(
['users', pageIndex, pageSize],
() => fetchUsers({ page: pageIndex + 1, limit: pageSize }),
);
const paginatedData = {
current_page: data?.current_page || 1,
last_page: data?.last_page || 1,
per_page: data?.per_page || 10,
total: data?.total || 0,
};
return (
<WPBasicDataTable
customColumn={columns}
tableData={data?.data || []}
isLoading={isLoading}
error={error}
paginatedData={paginatedData}
initialState={{ pageIndex, pageSize }}
/>
);
}
Styling & Theming
Table Variants
// Available variants
<V2WPBaseTable
variant='simple' // Simple table
variant='striped' // Striped rows
variant='outline' // Outlined table
variant='unstyled' // No styling
/>
Custom Styling
<WPBasicDataTable
customColumn={columns}
tableData={data}
tableBgColor='white'
tableHeadBgColor='gray.50'
sx={{
'& thead tr': {
bg: 'blue.50',
},
'& tbody tr:hover': {
bg: 'gray.50',
},
}}
/>
Responsive Tables
<Box overflowX='auto'>
<WPBasicDataTable customColumn={columns} tableData={data} minWidth='600px' />
</Box>
Table Context
Using Table Context
import { TableProvider, useTableState } from 'components/Tables/TableContext';
function MyTable() {
const { pagination, filters } = useTableState();
return (
<WPBasicDataTable
customColumn={columns}
tableData={data}
// Table will use context state
/>
);
}
function App() {
return (
<TableProvider>
<MyTable />
</TableProvider>
);
}
Table Actions
import { useTableDispatch } from 'components/Tables/TableContext';
function TableControls() {
const dispatch = useTableDispatch();
const handleResetPage = () => {
dispatch({ type: 'PAGE_RESET' });
};
const handleSetFilter = filters => {
dispatch({ type: 'SET_FILTERS', payload: filters });
};
return (
<HStack>
<Button onClick={handleResetPage}>Reset Page</Button>
<Button onClick={() => handleSetFilter({ status: 'active' })}>
Filter Active
</Button>
</HStack>
);
}
Performance Optimization
Memoization
const columns = useMemo(
() => [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Email', accessor: 'email' },
],
[],
);
const data = useMemo(() => tableData, [tableData]);
const MemoizedTable = memo(WPBasicDataTable);
Virtualization
// For large datasets
import { FixedSizeList as List } from 'react-window';
const VirtualizedRow = ({ index, style }) => (
<div style={style}>{/* Row content */}</div>
);
<List height={400} itemCount={data.length} itemSize={50}>
{VirtualizedRow}
</List>;
Lazy Loading
const TableComponent = lazy(() => import('components/Tables/WPBasicDataTable'));
<Suspense fallback={<TableSkeleton />}>
<TableComponent {...props} />
</Suspense>;
Accessibility
ARIA Support
- Tables include proper ARIA labels and roles
- Sortable columns have ARIA sort indicators
- Selected rows are announced to screen readers
- Loading states are announced
- Error states are accessible
Keyboard Navigation
- Tab navigation between interactive elements
- Arrow keys for row/column navigation
- Space/Enter for selection
- Sort controls are keyboard accessible
Screen Reader Support
- Table headers are properly associated
- Row/column counts are announced
- Selection state is announced
- Pagination state is accessible
Testing
Unit Testing
import { render, screen } from '@testing-library/react';
import WPBasicDataTable from 'components/Tables/WPBasicDataTable';
const mockData = [{ name: 'John', email: 'john@example.com' }];
const mockColumns = [
{ Header: 'Name', accessor: 'name' },
{ Header: 'Email', accessor: 'email' },
];
test('renders table with data', () => {
render(<WPBasicDataTable customColumn={mockColumns} tableData={mockData} />);
expect(screen.getByText('John')).toBeInTheDocument();
expect(screen.getByText('john@example.com')).toBeInTheDocument();
});
Integration Testing
import { render, screen, fireEvent } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
test('table selection works', async () => {
const user = userEvent.setup();
const onRowClick = jest.fn();
render(
<WPSelectDataTable
customColumn={mockColumns}
tableData={mockData}
onRowClick={onRowClick}
/>,
);
const checkbox = screen.getByRole('checkbox');
await user.click(checkbox);
expect(onRowClick).toHaveBeenCalledWith(expect.any(Array));
});
Migration Guide
From V1 to V2 Tables
// V1 (React Table v7)
import WPBasicDataTable from 'components/Tables/WPBasicDataTable';
<WPBasicDataTable customColumn={columns} tableData={data} />;
// V2 (TanStack Table v8)
import { V2WPBaseTable } from 'components/Tables/V2WPTables';
<V2WPBaseTable columns={columns} tableData={data} />;
Column Definition Changes
// V1 Column
{
Header: 'Name',
accessor: 'name',
Cell: ({ value }) => <span>{value}</span>
}
// V2 Column
{
header: 'Name',
accessorKey: 'name',
cell: info => <span>{info.getValue()}</span>
}
Common Issues
Performance Issues
- Use memoization for columns and data
- Implement pagination for large datasets
- Consider virtualization for very large tables
- Avoid complex calculations in cell renderers
Styling Issues
- Check Chakra UI theme compatibility
- Verify responsive behavior
- Test with different screen sizes
- Ensure proper contrast ratios
Data Issues
- Validate data structure matches columns
- Handle empty/null data gracefully
- Implement proper error boundaries
- Test with edge cases
Best Practices
- Column Configuration: Define columns outside component or use useMemo
- Data Handling: Validate and normalize data before passing to table
- Performance: Use pagination and memoization for large datasets
- Accessibility: Test with screen readers and keyboard navigation
- Error Handling: Implement proper error states and fallbacks
- Loading States: Show appropriate loading indicators
- Responsive Design: Test on various screen sizes
- Type Safety: Use TypeScript for better development experience