XinTable Component

XinTable is an out-of-the-box CRUD table component that generates a complete table page with search, create, edit, delete, pagination, sorting, and filtering capabilities through JSON configuration.

Core Features

  • Auto CRUD — Automatically connects to backend CRUD APIs with just api and accessName
  • JSON-driven Column Config — Column definitions control table display, search form, and edit form simultaneously
  • Search Form — Auto-generated from column config, supports expand/collapse
  • Keyword Search — Built-in quick search with remote fuzzy search support
  • Action Bar — Built-in create/edit/delete buttons with permission control
  • Table Tools — Refresh, density toggle, border toggle, column visibility settings
  • Form Reuse — Create and edit share the same modal form, distinguished by FormMode
  • Fully Customizable — All built-in behaviors can be replaced via callbacks

Basic Usage

import XinTable from '@/components/XinTable';
import type { XinTableColumn } from '@/components/XinTable/typings';
import type { XinTableInstance } from '@/components/XinTable/typings';

const columns: XinTableColumn<User>[] = [
  { dataIndex: 'id', title: 'ID', valueType: 'digit', hideInForm: true },
  { dataIndex: 'username', title: 'Username', valueType: 'text' },
  { dataIndex: 'email', title: 'Email', valueType: 'text' },
  { dataIndex: 'status', title: 'Status', valueType: 'select', fieldProps: {
    options: [{ label: 'Enabled', value: 1 }, { label: 'Disabled', value: 0 }]
  }},
  { dataIndex: 'created_at', title: 'Created At', valueType: 'date', hideInForm: true },
];

<XinTable<User>
  api="/admin/user"
  accessName="user"
  rowKey="id"
  columns={columns}
/>

This single configuration generates a complete user management page.

Column Configuration (XinTableColumn)

XinTableColumn extends both FormColumn and TableColumnType, controlling table column, search form, and edit form display from a single definition.

Extended Properties

PropertyTypeDefaultDescription
dataIndexstring-Field name
titlestring-Column header / field label
valueTypeFieldValue-Field type, determines the rendered component (same as XinForm)
fieldPropsobject-Props passed to the field component
hideInSearchbooleanfalseHide in the search form
hideInFormbooleanfalseHide in the create/edit form
hideInTablebooleanfalseHide in the table
hideInCreatebooleanfalseHide only in the create form
hideInUpdatebooleanfalseHide only in the edit form
searchFormColumn-Independent config for the search form (overrides defaults)

Column Visibility Example

const columns: XinTableColumn[] = [
  {
    dataIndex: 'id',
    title: 'ID',
    valueType: 'digit',
    hideInForm: true,    // Hidden in both create and edit
    hideInSearch: true,  // Hidden in search form
  },
  {
    dataIndex: 'password',
    title: 'Password',
    valueType: 'password',
    hideInTable: true,   // Hidden in table
    hideInSearch: true,
    hideInUpdate: true,  // Hidden only when editing (shown when creating)
  },
];

Display Controls

Toggle Properties

PropertyTypeDefaultDescription
addShowbooleantrueShow the create button
editShowboolean | ((record: T) => boolean)trueShow the edit button, supports row-level function
deleteShowboolean | ((record: T) => boolean)trueShow the delete button, supports row-level function
searchShowbooleantrueShow the advanced search bar
operateShowbooleantrueShow the operate column
paginationShowbooleantrueShow pagination
keywordSearchShowbooleantrueShow the quick search input

Row-Level Edit/Delete Control

<XinTable
  columns={columns}
  editShow={(record) => record.status === 'active'}   // Only active rows can be edited
  deleteShow={(record) => record.role !== 'admin'}    // Admin users cannot be deleted
/>

Search Features

Advanced Search (SearchForm)

Click the "Search" button in the action bar to expand/collapse the advanced search form. Search columns are auto-generated from columns where hideInSearch !== true.

<XinTable
  columns={columns}
  searchProps={{
    grid: true,
    rowProps: { gutter: [16, 16] },
    submitter: {
      submitText: 'Search',
      resetText: 'Clear',
    },
  }}
/>

A built-in quick search input triggers a remote search on Enter or icon click. The keywordSearch field is included in the request params.

Custom Search Field Configuration

When a field needs different configuration in the search form vs. table/form, use the search property to override:

{
  dataIndex: 'status',
  title: 'Status',
  valueType: 'select',
  fieldProps: { options: [{ label: 'Enabled', value: 1 }, { label: 'Disabled', value: 0 }] },
  // Allow multi-select and clear in search form
  search: {
    valueType: 'select',
    fieldProps: {
      mode: 'multiple',
      allowClear: true,
      options: [{ label: 'Enabled', value: 1 }, { label: 'Disabled', value: 0 }],
    },
  },
}

Customizing Action Bars

Top Action Bar (actionBarRender)

Customize the create button, search toggle, and keyword search input:

<XinTable
  columns={columns}
  actionBarRender={(buttons) => [
    buttons.add,
    buttons.keywordSearch,  // Show only create and keyword search
  ]}
/>

Tool Bar (toolBarRender)

Customize refresh, density, border, and column settings tools:

<XinTable
  columns={columns}
  toolBarRender={(buttons) => [
    buttons.reload,
    buttons.columnSetting,   // Show only refresh and column settings
  ]}
/>

Operate Column (operateRender)

Customize the action buttons for each table row:

<XinTable
  columns={columns}
  operateRender={(record, defaultButtons) => [
    defaultButtons.edit,
    <Button key="view" onClick={() => viewDetail(record)}>View</Button>,
    defaultButtons.del,
  ]}
/>

Set operate column properties via operateProps:

<XinTable
  columns={columns}
  operateProps={{
    width: 200,
    fixed: 'right',
  }}
/>

Custom Requests

Custom Data Request

<XinTable
  columns={columns}
  handleRequest={async (params) => {
    const res = await customApi.getList(params);
    return { data: res.list, total: res.total };
  }}
/>

Request Parameter Preprocessing

<XinTable
  columns={columns}
  requestParams={(params) => ({
    ...params,
    includeDeleted: false,
    extraFilter: { org_id: currentOrg.id },
  })}
/>

Custom Form Submit

<XinTable
  columns={columns}
  handleFinish={async (values, mode, formRef, defaultValue) => {
    if (mode === 'create') {
      await customCreate(values);
    } else {
      await customUpdate(defaultValue?.id, values);
    }
    return true; // Return true to close modal and refresh table
  }}
/>

XinTableInstance Methods

Access the table instance via tableRef:

import { useRef } from 'react';
import type { XinTableInstance } from '@/components/XinTable/typings';

const tableRef = useRef<XinTableInstance>(null);

// External controls
<Button onClick={() => tableRef.current?.reload()}>Refresh</Button>
<Button onClick={() => tableRef.current?.reset()}>Reset</Button>

<XinTable tableRef={tableRef} ... />

Instance Methods

MethodTypeDescription
reload() => Promise<void>Refresh table (keeps current page and search params)
reset() => Promise<void>Reset search params and go to first page
getDataSource() => T[]Get current table data
setDataSourceDispatch<SetStateAction<T[]>>Set table data
getTotal() => numberGet total count
getLoading() => booleanGet loading state
setLoadingDispatch<SetStateAction<boolean>>Set loading state
setPageInfo(page?: number, pageSize?: number) => voidSet pagination params
getForm() => XinFormRef<T> | nullGet create/edit form instance
getSearchForm() => FormInstance<T>Get search form instance

Table Tools

Four built-in tool buttons: refresh, density toggle, border toggle, and column settings.

Density Toggle

ModeDescription
largeDefault size
middleMedium density
smallCompact mode

Column Settings

Toggle column visibility via a Tree checkbox control, with changes applied instantly.

Permission Control

accessName is used for both API path construction and button permission control:

  • {accessName}.create — Create button
  • {accessName}.update — Edit button
  • {accessName}.delete — Delete button

Buttons are wrapped with the AuthButton component and will not render when the user lacks the required permission.

XinTableProps Full Reference

PropertyTypeDescription
apistringRequired, CRUD API endpoint
accessNamestringRequired, permission identifier prefix
rowKeystringRequired, primary key field name
columnsXinTableColumn<T>[]Required, column configuration
tableRefRefObject<XinTableInstance<T>>Table instance reference
addShowbooleanShow create button (default true)
editShowboolean | ((record: T) => boolean)Show edit button (default true)
deleteShowboolean | ((record: T) => boolean)Show delete button (default true)
searchShowbooleanShow advanced search (default true)
operateShowbooleanShow operate column (default true)
paginationShowbooleanShow pagination (default true)
keywordSearchShowbooleanShow quick search input (default true)
formPropsXinFormProps | falseCreate/edit form props, false to disable
modalPropsXinFormProps['modalProps']Modal properties
searchPropsSearchFormProps | falseSearch bar props, false to disable
operatePropsTableColumnType<T>Operate column properties
cardPropsCardPropsOuter card properties
paginationPaginationPropsPagination config (except current, total, onChange)
actionBarRender(dom: ActionNode) => ReactNode[]Custom top action bar
toolBarRender(dom: ToolBarNode) => ReactNode[]Custom tool bar
operateRender(record: T, dom: OperateNode) => ReactNode[]Custom operate column
handleRequest(params: RequestParams) => Promise<{ data: T[]; total: number }>Custom data request
requestParams(params: RequestParams) => RequestParamsRequest param preprocessing
handleFinish(values, mode, formRef, defaultValue?) => Promise<boolean>Custom form submit

Also inherits all TableProps<T> properties except columns, rowKey, onChange, pagination.

Request Parameters

interface RequestParams {
  page?: number;          // Current page, default 1
  pageSize?: number;      // Page size, default 10
  keywordSearch?: string; // Keyword search value
  filterValues?: Record<string, any>;  // Column filter values
  sorterValue?: { field: string; order: 'asc' | 'desc' };  // Sort params
}

API Convention

The built-in CRUD methods follow this API convention:

OperationMethodPathBody
ListGET{api}Query params
CreatePOST{api}Body JSON
UpdatePUT{api}/{id}Body JSON
DeleteDELETE{api}/{id}-

To use a different API format, override via handleRequest and handleFinish.

Complete Example

import { useRef } from 'react';
import XinTable from '@/components/XinTable';
import type { XinTableColumn, XinTableInstance } from '@/components/XinTable/typings';
import { Button, message } from 'antd';

interface User {
  id: number;
  username: string;
  email: string;
  role: string;
  status: number;
  created_at: string;
}

const userColumns: XinTableColumn<User>[] = [
  {
    dataIndex: 'id',
    title: 'ID',
    valueType: 'digit',
    hideInForm: true,
    hideInSearch: true,
    width: 80,
  },
  {
    dataIndex: 'username',
    title: 'Username',
    valueType: 'text',
    colProps: { span: 12 },
    fieldProps: { maxLength: 50 },
    rules: [{ required: true, message: 'Please enter a username' }],
  },
  {
    dataIndex: 'email',
    title: 'Email',
    valueType: 'text',
    colProps: { span: 12 },
    rules: [
      { required: true, message: 'Please enter an email' },
      { type: 'email', message: 'Invalid email format' },
    ],
  },
  {
    dataIndex: 'role',
    title: 'Role',
    valueType: 'select',
    colProps: { span: 12 },
    fieldProps: {
      options: [
        { label: 'Admin', value: 'admin' },
        { label: 'User', value: 'user' },
      ],
    },
    // Allow multi-select in search
    search: {
      valueType: 'select',
      fieldProps: {
        mode: 'multiple',
        allowClear: true,
        options: [
          { label: 'Admin', value: 'admin' },
          { label: 'User', value: 'user' },
        ],
      },
    },
  },
  {
    dataIndex: 'status',
    title: 'Status',
    valueType: 'radioButton',
    colProps: { span: 12 },
    fieldProps: {
      options: [
        { label: 'Enabled', value: 1 },
        { label: 'Disabled', value: 0 },
      ],
    },
  },
  {
    dataIndex: 'created_at',
    title: 'Created At',
    valueType: 'date',
    hideInForm: true,
  },
];

export default function UserTable() {
  const tableRef = useRef<XinTableInstance<User>>(null);

  return (
    <XinTable<User>
      tableRef={tableRef}
      api="/admin/user"
      accessName="user"
      rowKey="id"
      columns={userColumns}
      // Search config
      searchProps={{
        submitter: {
          submitText: 'Search',
          resetText: 'Clear',
        },
      }}
      // Form config
      modalProps={{ width: 600 }}
      // Pagination config
      pagination={{
        pageSize: 15,
        pageSizeOptions: ['10', '15', '20', '50'],
      }}
      // Card config
      cardProps={{ variant: 'borderless' }}
      // Row-level control
      editShow={(record) => record.status === 1}
      // Custom operate column
      operateRender={(record, buttons) => [
        <Button
          key="resetPwd"
          size="small"
          onClick={() => message.info(`Reset password: ${record.username}`)}
        >
          Reset Password
        </Button>,
        buttons.edit,
        buttons.del,
      ]}
    />
  );
}