@ggwan/mdata-table
v0.1.6
Published
A reusable React DataTable component built with TanStack Table
Maintainers
Readme
@ggwan/MData Table
A reusable React DataTable component built with TanStack Table, TypeScript, and Tailwind CSS.
Features
- ✅ Infinite Scrolling - Automatically load more data as you scroll
- ✅ Search & Filtering - Built-in search with customizable additional filters
- ✅ Sorting - Click column headers to sort data
- ✅ Row Selection - Select single or multiple rows with checkboxes
- ✅ Column Visibility - Toggle column visibility with dropdown menu
- ✅ Export - Export all data or selected rows to CSV
- ✅ Bulk Actions - Perform actions on selected rows
- ✅ Customizable - Flexible styling and configuration
- ✅ TypeScript - Full TypeScript support with proper types
- ✅ Responsive - Works on mobile and desktop
- ✅ Local Storage - Remembers table state and filters
- ✅ Client-side & Server-side - Supports both pagination modes
Installation
npm install @ggwan/mdata-table
# or
pnpm add @ggwan/mdata-table
# or
yarn add @ggwan/mdata-tablePeer Dependencies
This package requires the following peer dependencies:
npm install react react-dom @tanstack/react-table lucide-reactUsage
Basic Example
import { DataTable } from '@ggwan/mdata-table'
import type { ColumnDef } from '@tanstack/react-table'
interface User {
id: number
name: string
email: string
username: string
phone: string
website: string
}
}
const columns: ColumnDef<User>[] = [
{
id: 'select',
header: ({ table }) => (
<input
type="checkbox"
checked={table.getIsAllPageRowsSelected()}
onChange={(e) => table.toggleAllPageRowsSelected(e.target.checked)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<input
type="checkbox"
checked={row.getIsSelected()}
onChange={(e) => row.toggleSelected(e.target.checked)}
aria-label="Select row"
/>
),
},
{
accessorKey: 'name',
header: 'Name',
},
{
accessorKey: 'email',
header: 'Email',
},
{
accessorKey: 'username',
header: 'Username',
},
{
accessorKey: 'phone',
header: 'Phone',
},
{
accessorKey: 'website',
header: 'Website',
cell: ({ getValue }) => {
const website = getValue() as string;
return (
<a
href={`https://${website}`}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:underline"
>
{website}
</a>
);
},
},
]
function MyComponent() {
return (
<DataTable
columns={columns}
endpoint="https://jsonplaceholder.typicode.com/users"
tableId="users-table"
pageSize={5}
enableExport={true}
additionalFilters={[
{
key: 'username',
label: 'Username',
type: 'text',
placeholder: 'Filter by username...',
},
{
key: 'email',
label: 'Email',
type: 'text',
placeholder: 'Filter by email...',
},
]}
/>
)
}Using the Hook Directly
import { useDataTable } from '@ggwan/mdata-table'
function CustomTable() {
const {
data,
isLoading,
searchQuery,
setSearchQuery,
filters,
handleFilterChange,
sorting,
setSorting,
totalRecords,
exportData,
resetAllFilters,
} = useDataTable({
endpoint: 'https://jsonplaceholder.typicode.com/users',
pageSize: 10,
tableId: 'my-table',
})
// Your custom table implementation
return (
<div>
<input
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
placeholder="Search..."
/>
{/* Your custom table JSX */}
<div>Total: {totalRecords} records</div>
</div>
)
}
## API Reference
### DataTable Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `ColumnDef<TData>[]` | **required** | Column definitions using TanStack Table format |
| `endpoint` | `string` | **required** | API endpoint URL for data fetching |
| `tableId` | `string` | **required** | Unique identifier for the table (used for localStorage) |
| `pageSize` | `number` | `20` | Number of items per page |
| `enableExport` | `boolean` | `false` | Enable export functionality |
| `additionalFilters` | `AdditionalFilter[]` | `[]` | Additional filter controls |
| `onBulkDelete` | `() => void` | `undefined` | Callback for bulk delete action |
| `onBulkExportSelected` | `() => void` | `undefined` | Callback for exporting selected rows |
### AdditionalFilter
| Property | Type | Description |
|----------|------|-------------|
| `key` | `string` | Filter field key |
| `label` | `string` | Display label for the filter |
| `type` | `'text' \| 'select' \| 'number'` | Filter input type |
| `placeholder` | `string` | Placeholder text (for text/number inputs) |
| `options` | `Array<{value: string, label: string}>` | Options for select filters |
### useDataTable Hook
#### Parameters
```typescript
interface UseDataTableOptions {
endpoint: string
pageSize: number
tableId: string
}Returns
interface UseDataTableReturn<TData> {
data: TData[]
searchQuery: string
setSearchQuery: (query: string) => void
isLoading: boolean
isFetchingNextPage: boolean
sorting: SortingState | null
setSorting: (sorting: SortingState | null) => void
hasMore: boolean
filters: FilterState
handleFilterChange: (key: string, value: string | number | boolean) => void
showAdditionalFilters: boolean
setShowAdditionalFilters: (show: boolean) => void
loadMoreRef: React.RefObject<HTMLDivElement>
totalRecords: number
resetAllFilters: () => void
exportData: () => Promise<TData[]>
isExporting: boolean
rowSelection: Record<string, boolean>
setRowSelection: (selection: Record<string, boolean>) => void
}API Response Format
The DataTable supports two response formats:
1. Array Response (like JSONPlaceholder)
[
{ "id": 1, "name": "John", "email": "[email protected]" },
{ "id": 2, "name": "Jane", "email": "[email protected]" }
]2. Object Response (with pagination)
{
"data": [
{ "id": 1, "name": "John", "email": "[email protected]" }
],
"total": 100,
"hasMore": true,
"next_page_url": "https://api.example.com/users?page=2"
}Styling
The component uses Tailwind CSS classes. You can customize the appearance by:
- Using CSS Variables: Override the built-in CSS variables
- Custom Classes: Pass custom className props
- Tailwind Config: Extend your Tailwind configuration
CSS Variables
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--muted: 210 40% 96%;
--border: 214.3 31.8% 91.4%;TypeScript Support
This package is written in TypeScript and provides full type safety. All types are exported:
import type {
AdditionalFilter,
FilterState,
SortingState,
UseDataTableOptions,
UseDataTableReturn
} from '@ggwan/mdata-table'Features in Detail
🔍 Search & Filtering
- Global search: Search across all visible columns
- Additional filters: Add custom filter controls (text, select, number)
- Real-time filtering: Results update as you type
- Filter persistence: Filters are remembered in localStorage
📊 Sorting
- Column sorting: Click any column header to sort
- Multi-directional: Ascending, descending, and no sort
- Type-aware: Proper sorting for strings, numbers, and dates
- Sort persistence: Last sort order is remembered
🎯 Row Selection
- Individual selection: Click checkbox on any row
- Select all: Master checkbox to select all visible rows
- Bulk actions: Perform actions on multiple selected rows
- Selection persistence: Selections maintained during pagination
📱 Responsive Design
- Mobile-friendly: Optimized for small screens
- Touch interactions: Proper touch targets for mobile
- Adaptive layout: Adjusts to different screen sizes
⚡ Performance
- Infinite scrolling: Smooth pagination without page reloads
- Client-side optimization: Smart filtering and sorting
- Lazy loading: Only render visible rows
- Memory efficient: Proper cleanup and state management
Troubleshooting
Common Issues
- Styles not loading: Make sure to import the CSS
- TypeScript errors: Ensure all peer dependencies are installed
- Data not showing: Check your API response format
- Filtering not working: Verify filter key matches your data structure
Debug Mode
Enable debug logging by setting localStorage:
localStorage.setItem('mdata-table-debug', 'true')Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Changelog
v1.0.0
- Initial release
- DataTable component with infinite scrolling
- Search and filtering capabilities
- Row selection and bulk actions
- Export functionality
- Column visibility toggle
- TypeScript support
- Responsive design accessorKey: 'name', header: 'Name', }, { accessorKey: 'email', header: 'Email', }, { accessorKey: 'role', header: 'Role', }, ]
function MyComponent() { return ( <DataTable columns={columns} endpoint="/api/users" tableId="users-table" pageSize={20} enableExport={true} additionalFilters={[ { key: 'role', label: 'Role', type: 'select', options: [ { value: 'admin', label: 'Admin' }, { value: 'user', label: 'User' }, ], }, { key: 'department', label: 'Department', type: 'text', placeholder: 'Search by department...', }, ]} renderSubComponent={(row) => ( User Details ID: {row.original.id} Email: {row.original.email} )} /> ) }
## API Reference
### DataTable Props
| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `columns` | `ColumnDef<TData, TValue>[]` | - | **Required.** Column definitions for the table |
| `endpoint` | `string` | - | **Required.** API endpoint for fetching data |
| `tableId` | `string` | - | **Required.** Unique identifier for the table (used for localStorage) |
| `pageSize` | `number` | `5` | Number of items per page |
| `additionalFilters` | `AdditionalFilter[]` | `[]` | Additional filter options |
| `enableExport` | `boolean` | `false` | Enable CSV export functionality |
| `renderSubComponent` | `(row: Row<TData>) => ReactNode` | - | Render expandable row content |
### API Response Format
Your API endpoint should return data in this format:
```json
{
"data": [...], // Array of data items
"total": 100, // Total number of records
"hasMore": true // Whether there are more pages
}API Parameters
The component will send these query parameters to your endpoint:
page- Current page numberlimit- Items per pagesearch- Search querysortBy- Column to sort bysortOrder- Sort direction ('asc' or 'desc')- Additional filter parameters
Styling
This component uses Tailwind CSS. Make sure to include the styles in your project:
import 'mdata-table/styles'Or if you want to customize the styles, you can import the base CSS and override with your own classes.
TypeScript
The package includes full TypeScript definitions. Key types:
import type {
AdditionalFilter,
FilterState,
SortingState,
UseDataTableReturn,
UseDataTableOptions
} from 'mdata-table'License
MIT License - see LICENSE file for details.
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Remove tseslint.configs.recommended and replace with this
...tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
...tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
...tseslint.configs.stylisticTypeChecked,
// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])You can also install eslint-plugin-react-x and eslint-plugin-react-dom for React-specific lint rules:
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'
export default tseslint.config([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])