DEV Community

Michael Turner
Michael Turner

Posted on

Advanced Data Table Implementation with ka-table in React

ka-table is a powerful, flexible table component for React that provides advanced features including sorting, filtering, grouping, editing, and virtualization. It's designed with a plugin-based architecture that allows extensive customization and extension. This guide walks through implementing advanced table features using ka-table with React, covering complex data manipulation, custom plugins, and enterprise-grade implementations. This is part 9 of a series on using ka-table with React.

Prerequisites

Before you begin, ensure you have:

  • Node.js version 16.0 or higher
  • npm, yarn, or pnpm package manager
  • A React project (version 16.8 or higher) with hooks support
  • Advanced understanding of React hooks, context API, and state management
  • Familiarity with TypeScript (highly recommended)
  • Knowledge of functional programming concepts

Installation

Install ka-table and its dependencies:

npm install ka-table
Enter fullscreen mode Exit fullscreen mode

Or with yarn:

yarn add ka-table
Enter fullscreen mode Exit fullscreen mode

Or with pnpm:

pnpm add ka-table
Enter fullscreen mode Exit fullscreen mode

Your package.json should include:

{
  "dependencies": {
    "ka-table": "^9.0.0",
    "react": "^18.0.0",
    "react-dom": "^18.0.0"
  }
}
Enter fullscreen mode Exit fullscreen mode

Project Setup

ka-table requires minimal setup. Import the necessary components and styles:

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import 'ka-table/style.css';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

First Example / Basic Usage

Let's create a basic table component. Create src/DataTable.jsx:

// src/DataTable.jsx
import React, { useState } from 'react';
import { Table } from 'ka-table';
import {
  DataType,
  SortingMode,
  FilteringMode,
  PagingPosition
} from 'ka-table/enums';
import { kaPropsUtils } from 'ka-table/utils';

function DataTable() {
  const [data, setData] = useState([
    { id: 1, name: 'John Doe', age: 28, email: 'john@example.com', department: 'Engineering' },
    { id: 2, name: 'Jane Smith', age: 32, email: 'jane@example.com', department: 'Marketing' },
    { id: 3, name: 'Bob Johnson', age: 45, email: 'bob@example.com', department: 'Sales' },
    { id: 4, name: 'Alice Williams', age: 29, email: 'alice@example.com', department: 'Engineering' }
  ]);

  const columns = [
    { key: 'id', title: 'ID', dataType: DataType.Number, width: 80 },
    { key: 'name', title: 'Name', dataType: DataType.String, width: 200 },
    { key: 'age', title: 'Age', dataType: DataType.Number, width: 100 },
    { key: 'email', title: 'Email', dataType: DataType.String, width: 250 },
    { key: 'department', title: 'Department', dataType: DataType.String, width: 150 }
  ];

  return (
    <div style={{ padding: '20px' }}>
      <h2>ka-table Data Table</h2>
      <Table
        columns={columns}
        data={data}
        rowKeyField="id"
        sortingMode={SortingMode.Single}
        filteringMode={FilteringMode.HeaderFilter}
        paging={{ enabled: true, pageSize: 10 }}
      />
    </div>
  );
}

export default DataTable;
Enter fullscreen mode Exit fullscreen mode

Understanding the Basics

ka-table uses a plugin-based architecture where:

  • columns: Array of column definition objects
  • data: Array of row data objects
  • rowKeyField: Unique identifier field for rows
  • Plugins: Enable features like sorting, filtering, paging, etc.

Key concepts for advanced usage:

  • Enums: Use enums for data types, sorting modes, filtering modes
  • Plugins: Extend functionality with custom plugins
  • Actions: Dispatch actions to modify table state
  • Props Utils: Utility functions for working with table props

Here's an example with editing and custom cell rendering:

// src/EditableTable.jsx
import React, { useState } from 'react';
import { Table } from 'ka-table';
import {
  DataType,
  SortingMode,
  FilteringMode,
  EditingMode
} from 'ka-table/enums';

function EditableTable() {
  const [data, setData] = useState([
    { id: 1, product: 'Laptop', price: 999.99, stock: 15, category: 'Electronics' },
    { id: 2, product: 'Mouse', price: 29.99, stock: 8, category: 'Electronics' },
    { id: 3, product: 'Keyboard', price: 79.99, stock: 12, category: 'Electronics' }
  ]);

  const columns = [
    { key: 'id', title: 'ID', dataType: DataType.Number, width: 80 },
    { 
      key: 'product', 
      title: 'Product', 
      dataType: DataType.String, 
      width: 200,
      editor: (props) => (
        <input
          type="text"
          value={props.value}
          onChange={(e) => props.onValueChange(e.target.value)}
        />
      )
    },
    { 
      key: 'price', 
      title: 'Price', 
      dataType: DataType.Number, 
      width: 120,
      format: (value) => `$${value.toFixed(2)}`
    },
    { 
      key: 'stock', 
      title: 'Stock', 
      dataType: DataType.Number, 
      width: 100,
      style: (value) => ({
        backgroundColor: value < 10 ? '#ffebee' : 'transparent'
      })
    },
    { key: 'category', title: 'Category', dataType: DataType.String, width: 150 }
  ];

  return (
    <div style={{ padding: '20px' }}>
      <h2>Editable Table</h2>
      <Table
        columns={columns}
        data={data}
        rowKeyField="id"
        editingMode={EditingMode.Cell}
        sortingMode={SortingMode.Single}
        filteringMode={FilteringMode.HeaderFilter}
      />
    </div>
  );
}

export default EditableTable;
Enter fullscreen mode Exit fullscreen mode

Practical Example / Building Something Real

Let's build a comprehensive admin dashboard table with advanced features:

// src/AdminDashboard.jsx
import React, { useState, useCallback } from 'react';
import { Table } from 'ka-table';
import {
  DataType,
  SortingMode,
  FilteringMode,
  EditingMode,
  PagingPosition,
  ActionType
} from 'ka-table/enums';
import { kaPropsUtils } from 'ka-table/utils';

function AdminDashboard() {
  const [data, setData] = useState([
    { 
      id: 1, 
      name: 'Sarah Johnson', 
      email: 'sarah@example.com',
      department: 'Engineering', 
      salary: 95000, 
      startDate: '2020-01-15',
      status: 'Active',
      role: 'Senior Developer'
    },
    { 
      id: 2, 
      name: 'Michael Chen', 
      email: 'michael@example.com',
      department: 'Marketing', 
      salary: 75000, 
      startDate: '2019-06-20',
      status: 'Active',
      role: 'Marketing Manager'
    },
    { 
      id: 3, 
      name: 'Emily Davis', 
      email: 'emily@example.com',
      department: 'Sales', 
      salary: 65000, 
      startDate: '2021-03-10',
      status: 'Active',
      role: 'Sales Representative'
    },
    { 
      id: 4, 
      name: 'David Wilson', 
      email: 'david@example.com',
      department: 'Engineering', 
      salary: 110000, 
      startDate: '2018-09-05',
      status: 'Active',
      role: 'Tech Lead'
    }
  ]);

  const [tableProps, changeTableProps] = useState({
    columns: [
      { 
        key: 'id', 
        title: 'ID', 
        dataType: DataType.Number, 
        width: 80,
        style: { textAlign: 'center' }
      },
      { 
        key: 'name', 
        title: 'Employee Name', 
        dataType: DataType.String, 
        width: 200,
        filterRowValue: '',
        editor: (props) => (
          <input
            type="text"
            value={props.value || ''}
            onChange={(e) => props.onValueChange(e.target.value)}
            style={{ width: '100%' }}
          />
        )
      },
      { 
        key: 'email', 
        title: 'Email', 
        dataType: DataType.String, 
        width: 250,
        filterRowValue: ''
      },
      { 
        key: 'department', 
        title: 'Department', 
        dataType: DataType.String, 
        width: 150,
        filterRowValue: '',
        editor: (props) => (
          <select
            value={props.value || ''}
            onChange={(e) => props.onValueChange(e.target.value)}
            style={{ width: '100%' }}
          >
            <option value="Engineering">Engineering</option>
            <option value="Marketing">Marketing</option>
            <option value="Sales">Sales</option>
            <option value="HR">HR</option>
          </select>
        )
      },
      { 
        key: 'role', 
        title: 'Role', 
        dataType: DataType.String, 
        width: 180,
        filterRowValue: ''
      },
      { 
        key: 'salary', 
        title: 'Salary', 
        dataType: DataType.Number, 
        width: 120,
        format: (value) => `$${value.toLocaleString()}`,
        style: (value) => ({
          backgroundColor: value > 100000 ? '#e8f5e9' : 'transparent',
          fontWeight: value > 100000 ? 'bold' : 'normal'
        }),
        editor: (props) => (
          <input
            type="number"
            value={props.value || 0}
            onChange={(e) => props.onValueChange(parseFloat(e.target.value) || 0)}
            style={{ width: '100%' }}
          />
        )
      },
      { 
        key: 'startDate', 
        title: 'Start Date', 
        dataType: DataType.Date, 
        width: 120,
        format: (value) => {
          if (!value) return '';
          const date = new Date(value);
          return date.toLocaleDateString('en-US');
        }
      },
      { 
        key: 'status', 
        title: 'Status', 
        dataType: DataType.String, 
        width: 120,
        filterRowValue: '',
        style: (value) => ({
          color: value === 'Active' ? '#28a745' : '#dc3545',
          fontWeight: 'bold'
        }),
        editor: (props) => (
          <select
            value={props.value || 'Active'}
            onChange={(e) => props.onValueChange(e.target.value)}
            style={{ width: '100%' }}
          >
            <option value="Active">Active</option>
            <option value="Inactive">Inactive</option>
          </select>
        )
      }
    ],
    data: data,
    rowKeyField: 'id',
    sortingMode: SortingMode.Single,
    filteringMode: FilteringMode.HeaderFilter,
    editingMode: EditingMode.Cell,
    paging: {
      enabled: true,
      pageSize: 10,
      pageIndex: 0,
      position: PagingPosition.Bottom
    }
  });

  const dispatch = useCallback((action) => {
    changeTableProps((prevState) => {
      switch (action.type) {
        case ActionType.UpdateCellValue:
          const updatedData = prevState.data.map((row) => {
            if (row[prevState.rowKeyField] === action.rowKeyValue) {
              return { ...row, [action.columnKey]: action.value };
            }
            return row;
          });
          setData(updatedData);
          return { ...prevState, data: updatedData };
        case ActionType.UpdateFilterRowValue:
          return kaPropsUtils.updateFilterRowValue(prevState, action);
        case ActionType.UpdateSortDirection:
          return kaPropsUtils.updateSortDirection(prevState, action);
        case ActionType.UpdatePageIndex:
          return kaPropsUtils.updatePageIndex(prevState, action);
        default:
          return prevState;
      }
    });
  }, []);

  const handleAddRow = () => {
    const newRow = {
      id: Math.max(...data.map(d => d.id)) + 1,
      name: 'New Employee',
      email: 'new@example.com',
      department: 'Engineering',
      role: 'Developer',
      salary: 50000,
      startDate: new Date().toISOString().split('T')[0],
      status: 'Active'
    };
    setData([...data, newRow]);
    changeTableProps(prev => ({ ...prev, data: [...data, newRow] }));
  };

  const handleDeleteRow = () => {
    const selectedRows = kaPropsUtils.getSelectedRows(tableProps);
    if (selectedRows.length > 0) {
      const newData = data.filter(row => 
        !selectedRows.some(selected => selected.id === row.id)
      );
      setData(newData);
      changeTableProps(prev => ({ ...prev, data: newData }));
    }
  };

  return (
    <div style={{ padding: '20px' }}>
      <div style={{ marginBottom: '20px', display: 'flex', gap: '10px', alignItems: 'center' }}>
        <h2>Admin Dashboard</h2>
        <button
          onClick={handleAddRow}
          style={{
            padding: '8px 16px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Add Row
        </button>
        <button
          onClick={handleDeleteRow}
          style={{
            padding: '8px 16px',
            backgroundColor: '#dc3545',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer'
          }}
        >
          Delete Selected
        </button>
      </div>
      <Table
        {...tableProps}
        dispatch={dispatch}
        childComponents={{
          cell: {
            content: (props) => {
              if (props.column.key === 'status') {
                return (
                  <span style={{ 
                    color: props.value === 'Active' ? '#28a745' : '#dc3545',
                    fontWeight: 'bold'
                  }}>
                    {props.value}
                  </span>
                );
              }
              return props.value;
            }
          }
        }}
      />
    </div>
  );
}

export default AdminDashboard;
Enter fullscreen mode Exit fullscreen mode

This advanced example demonstrates:

  • Cell editing with custom editors (text input, select dropdown, number input)
  • Custom cell rendering with conditional styling
  • Filtering with header filters
  • Sorting functionality
  • Pagination
  • Row selection and deletion
  • Real-time data updates
  • Custom formatting (currency, dates)
  • Conditional styling based on cell values

Common Issues / Troubleshooting

  1. Table not rendering: Ensure you've imported the CSS file (ka-table/style.css). Verify that your data array and columns array are properly structured.

  2. Editing not working: Set editingMode={EditingMode.Cell} and implement custom editors in column definitions. Make sure you're handling the dispatch action for UpdateCellValue.

  3. Filtering not working: Enable filteringMode={FilteringMode.HeaderFilter} and set filterRowValue: '' in column definitions. Implement the UpdateFilterRowValue action handler.

  4. Sorting not working: Set sortingMode={SortingMode.Single} or SortingMode.Multiple and implement the UpdateSortDirection action handler using kaPropsUtils.updateSortDirection.

  5. Performance issues: For large datasets, ensure virtualization is enabled (default) and consider implementing server-side pagination.

  6. TypeScript errors: Install type definitions if available. ka-table has built-in TypeScript support, so ensure your column and data types match the expected interfaces.

Next Steps

Now that you've mastered ka-table:

  • Explore advanced plugins and custom extensions
  • Implement server-side data loading and pagination
  • Add custom action handlers and event listeners
  • Learn about row grouping and aggregation
  • Explore custom cell templates and renderers
  • Add export functionality (CSV, Excel)
  • Check the official repository: https://github.com/komarovalexander/ka-table
  • Look for part 10 of this series for more advanced topics

Summary

You've learned how to implement advanced data table features with ka-table, including cell editing, custom rendering, filtering, sorting, and pagination. The plugin-based architecture provides extensive flexibility for building enterprise-grade data management interfaces with excellent performance and customization options.

Top comments (0)