CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-refinedev--core

React meta-framework for building enterprise CRUD applications with authentication, data management, and headless UI integration

Pending
Overview
Eval results
Files

tables-lists.mddocs/

Tables & Lists

Comprehensive table functionality with sorting, filtering, pagination, search, and URL synchronization for data-heavy interfaces.

Capabilities

Core Table Management

useTable Hook

Provides comprehensive table functionality including sorting, filtering, pagination, and URL synchronization for data-intensive applications.

/**
 * Provides comprehensive table functionality with data management
 * @param params - Table configuration options
 * @returns Table state, data, and control functions
 */
function useTable<TQueryFnData = BaseRecord, TError = HttpError, TData = TQueryFnData>(
  params?: UseTableConfig<TQueryFnData, TError, TData>
): UseTableReturnType<TData, TError>;

interface UseTableConfig<TQueryFnData, TError, TData> {
  /** Resource name - inferred from route if not provided */
  resource?: string;
  /** Pagination configuration */
  pagination?: {
    /** Initial current page */
    current?: number;
    /** Initial page size */
    pageSize?: number;
    /** Pagination mode - server, client, or disabled */
    mode?: "server" | "client" | "off";
  };
  /** Sorting configuration */
  sorters?: {
    /** Initial sort configuration */
    initial?: CrudSorting;
    /** Permanent sorts that cannot be changed */
    permanent?: CrudSorting;
    /** Sorting mode - server or disabled */
    mode?: "server" | "off";
  };
  /** Filtering configuration */
  filters?: {
    /** Initial filter configuration */
    initial?: CrudFilters;
    /** Permanent filters that cannot be changed */
    permanent?: CrudFilters;
    /** How to handle new filters with existing ones */
    defaultBehavior?: "merge" | "replace";
    /** Filtering mode - server or disabled */
    mode?: "server" | "off";
  };
  /** Whether to sync table state with URL */
  syncWithLocation?: boolean;
  /** Additional metadata */
  meta?: MetaQuery;
  /** Data provider name */
  dataProviderName?: string;
  /** Success notification configuration */
  successNotification?: SuccessErrorNotification | false;
  /** Error notification configuration */
  errorNotification?: SuccessErrorNotification | false;
  /** Live mode configuration */
  liveMode?: LiveModeProps;
  /** Callback for live events */
  onLiveEvent?: (event: LiveEvent) => void;
  /** Query options */
  queryOptions?: UseQueryOptions<GetListResponse<TQueryFnData>, TError>;
  /** Override mutation mode */
  mutationMode?: MutationMode;
  /** Timeout for undoable operations */
  undoableTimeout?: number;
  /** Cache invalidation configuration */
  invalidates?: Array<string>;
}

interface UseTableReturnType<TData, TError> {
  /** React Query result for table data */
  tableQuery: UseQueryResult<GetListResponse<TData>, TError>;
  /** Current sorting configuration */
  sorters: CrudSorting;
  /** Function to update sorting */
  setSorters: (sorters: CrudSorting) => void;
  /** Current filtering configuration */
  filters: CrudFilters;
  /** Function to update filters */
  setFilters: (filters: CrudFilters, behavior?: "merge" | "replace") => void;
  /** Current page number */
  current: number;
  /** Function to set current page */
  setCurrent: (page: number) => void;
  /** Current page size */
  pageSize: number;
  /** Function to set page size */
  setPageSize: (size: number) => void;
  /** Total page count */
  pageCount: number;
  /** Create link function for external sync */
  createLinkForSyncWithLocation: (params: SyncWithLocationParams) => string;
  /** Processed table result */
  result: {
    data: TData[];
    total: number;
  };
  /** Loading overtime information */
  overtime: UseLoadingOvertimeReturnType;
}

interface SyncWithLocationParams {
  /** Pagination parameters */
  pagination?: {
    current?: number;
    pageSize?: number;
  };
  /** Sorting parameters */
  sorters?: CrudSorting;
  /** Filtering parameters */
  filters?: CrudFilters;
}

Usage Example:

import { useTable } from "@refinedev/core";
import { useState } from "react";

interface Post {
  id: number;
  title: string;
  content: string;
  status: "draft" | "published";
  createdAt: string;
  author: {
    name: string;
  };
}

function PostsTable() {
  const {
    tableQuery,
    sorters,
    setSorters,
    filters,
    setFilters,
    current,
    setCurrent,
    pageSize,
    setPageSize,
    pageCount
  } = useTable<Post>({
    resource: "posts",
    pagination: {
      current: 1,
      pageSize: 10,
      mode: "server"
    },
    sorters: {
      initial: [{
        field: "createdAt",
        order: "desc"
      }],
      mode: "server"
    },
    filters: {
      initial: [{
        field: "status",
        operator: "eq",
        value: "published"
      }],
      mode: "server"
    },
    syncWithLocation: true
  });
  
  const { data, isLoading, error } = tableQuery;
  
  const handleSort = (field: string) => {
    const existingSorter = sorters.find(s => s.field === field);
    if (existingSorter) {
      const newOrder = existingSorter.order === "asc" ? "desc" : "asc";
      setSorters([{ field, order: newOrder }]);
    } else {
      setSorters([{ field, order: "asc" }]);
    }
  };
  
  const handleFilter = (field: string, value: string) => {
    setFilters([{
      field,
      operator: "contains",
      value
    }], "merge");
  };
  
  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  
  return (
    <div>
      {/* Search filters */}
      <div className="filters">
        <input
          placeholder="Search by title..."
          onChange={(e) => handleFilter("title", e.target.value)}
        />
        <select onChange={(e) => handleFilter("status", e.target.value)}>
          <option value="">All Status</option>
          <option value="draft">Draft</option>
          <option value="published">Published</option>
        </select>
      </div>
      
      {/* Table */}
      <table>
        <thead>
          <tr>
            <th onClick={() => handleSort("title")}>
              Title {getSortIcon("title", sorters)}
            </th>
            <th onClick={() => handleSort("status")}>
              Status {getSortIcon("status", sorters)}
            </th>
            <th onClick={() => handleSort("createdAt")}>
              Created {getSortIcon("createdAt", sorters)}
            </th>
            <th>Author</th>
          </tr>
        </thead>
        <tbody>
          {data?.data.map(post => (
            <tr key={post.id}>
              <td>{post.title}</td>
              <td>{post.status}</td>
              <td>{new Date(post.createdAt).toLocaleDateString()}</td>
              <td>{post.author.name}</td>
            </tr>
          ))}
        </tbody>
      </table>
      
      {/* Pagination */}
      <div className="pagination">
        <button
          onClick={() => setCurrent(current - 1)}
          disabled={current === 1}
        >
          Previous
        </button>
        
        <span>
          Page {current} of {pageCount} (Total: {data?.total})
        </span>
        
        <button
          onClick={() => setCurrent(current + 1)}
          disabled={current === pageCount}
        >
          Next
        </button>
        
        <select
          value={pageSize}
          onChange={(e) => setPageSize(parseInt(e.target.value))}
        >
          <option value={10}>10 per page</option>
          <option value={20}>20 per page</option>
          <option value={50}>50 per page</option>
        </select>
      </div>
    </div>
  );
}

function getSortIcon(field: string, sorters: CrudSorting) {
  const sorter = sorters.find(s => s.field === field);
  if (!sorter) return "↕️";
  return sorter.order === "asc" ? "↑" : "↓";
}

Select & Dropdown Management

useSelect Hook

Specialized hook for select/dropdown components with search, pagination, and option management.

/**
 * Manages select/dropdown components with search and pagination
 * @param params - Select configuration options
 * @returns Select state, options, and control functions
 */
function useSelect<TQueryFnData = BaseRecord, TError = HttpError, TData = TQueryFnData>(
  params: UseSelectConfig<TQueryFnData, TError, TData>
): UseSelectReturnType<TData, TError>;

interface UseSelectConfig<TQueryFnData, TError, TData> {
  /** Resource name for fetching options */
  resource: string;
  /** Field to use as option label */
  optionLabel?: keyof TData | string;
  /** Field to use as option value */
  optionValue?: keyof TData | string;
  /** Field to search in */
  searchField?: string;
  /** Additional filters */
  filters?: CrudFilters;
  /** Sorting configuration */
  sorters?: CrudSorting;
  /** Default selected value(s) */
  defaultValue?: BaseKey | BaseKey[];
  /** Search debounce delay in milliseconds */
  debounce?: number;
  /** Query options */
  queryOptions?: UseQueryOptions<GetListResponse<TQueryFnData>, TError>;
  /** Pagination configuration */
  pagination?: {
    current?: number;
    pageSize?: number;
    mode?: "server" | "client" | "off";
  };
  /** Additional metadata */
  meta?: MetaQuery;
  /** Data provider name */
  dataProviderName?: string;
  /** Success notification configuration */
  successNotification?: SuccessErrorNotification | false;
  /** Error notification configuration */
  errorNotification?: SuccessErrorNotification | false;
  /** Live mode configuration */
  liveMode?: LiveModeProps;
  /** Callback for live events */
  onLiveEvent?: (event: LiveEvent) => void;
}

interface UseSelectReturnType<TData, TError> {
  /** React Query result */
  query: UseQueryResult<GetListResponse<TData>, TError>;
  /** Query result (alias) */
  queryResult: UseQueryResult<GetListResponse<TData>, TError>;
  /** Formatted options for select component */
  options: SelectOption[];
  /** Search handler function */
  onSearch: (value: string) => void;
  /** Current search value */
  search: string;
}

interface SelectOption {
  /** Option label for display */
  label: string;
  /** Option value */
  value: BaseKey;
  /** Raw data object */
  data?: BaseRecord;
}

Usage Example:

import { useSelect } from "@refinedev/core";
import { useState } from "react";

interface Category {
  id: number;
  name: string;
  description?: string;
}

function CategorySelect({ value, onChange }: { value?: number; onChange: (value: number) => void }) {
  const { options, onSearch, query } = useSelect<Category>({
    resource: "categories",
    optionLabel: "name",
    optionValue: "id",
    searchField: "name",
    sorters: [{
      field: "name",
      order: "asc"
    }],
    debounce: 500,
    pagination: {
      pageSize: 50,
      mode: "server"
    }
  });
  
  const [searchValue, setSearchValue] = useState("");
  
  const handleSearch = (value: string) => {
    setSearchValue(value);
    onSearch(value);
  };
  
  return (
    <div className="category-select">
      <input
        type="text"
        placeholder="Search categories..."
        value={searchValue}
        onChange={(e) => handleSearch(e.target.value)}
      />
      
      <select
        value={value || ""}
        onChange={(e) => onChange(parseInt(e.target.value))}
        disabled={query.isLoading}
      >
        <option value="">Select a category</option>
        {options.map(option => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
      
      {query.isLoading && <span>Loading...</span>}
      {query.error && <span>Error loading categories</span>}
    </div>
  );
}

// Usage with multiple selection
function MultiCategorySelect({ 
  values = [], 
  onChange 
}: { 
  values?: number[]; 
  onChange: (values: number[]) => void; 
}) {
  const { options, onSearch } = useSelect<Category>({
    resource: "categories",
    optionLabel: "name",
    optionValue: "id",
    defaultValue: values
  });
  
  const handleToggle = (value: number) => {
    if (values.includes(value)) {
      onChange(values.filter(v => v !== value));
    } else {
      onChange([...values, value]);
    }
  };
  
  return (
    <div className="multi-select">
      <input
        type="text"
        placeholder="Search categories..."
        onChange={(e) => onSearch(e.target.value)}
      />
      
      <div className="options">
        {options.map(option => (
          <label key={option.value}>
            <input
              type="checkbox"
              checked={values.includes(option.value as number)}
              onChange={() => handleToggle(option.value as number)}
            />
            {option.label}
          </label>
        ))}
      </div>
    </div>
  );
}

Advanced Table Features

Infinite Scrolling Tables

Implementation of infinite scrolling for large datasets using useInfiniteList.

/**
 * Infinite scrolling table implementation
 */
interface InfiniteTableConfig<TData> {
  /** Resource name */
  resource: string;
  /** Items per page */
  pageSize?: number;
  /** Sorting configuration */
  sorters?: CrudSorting;
  /** Filtering configuration */
  filters?: CrudFilters;
  /** Threshold for loading more data */
  threshold?: number;
}

interface InfiniteTableReturnType<TData> {
  /** All loaded data */
  data: TData[];
  /** Whether more data is available */
  hasNextPage: boolean;
  /** Function to load more data */
  fetchNextPage: () => void;
  /** Whether next page is loading */
  isFetchingNextPage: boolean;
  /** Total count of items */
  total?: number;
}

Infinite Scroll Example:

import { useInfiniteList } from "@refinedev/core";
import { useEffect, useRef } from "react";

function InfinitePostsList() {
  const {
    data,
    hasNextPage,
    fetchNextPage,
    isFetchingNextPage
  } = useInfiniteList({
    resource: "posts",
    pagination: {
      pageSize: 20
    }
  });
  
  const loadMoreRef = useRef<HTMLDivElement>(null);
  
  useEffect(() => {
    const observer = new IntersectionObserver(
      (entries) => {
        if (entries[0].isIntersecting && hasNextPage && !isFetchingNextPage) {
          fetchNextPage();
        }
      },
      { threshold: 1.0 }
    );
    
    if (loadMoreRef.current) {
      observer.observe(loadMoreRef.current);
    }
    
    return () => observer.disconnect();
  }, [hasNextPage, isFetchingNextPage, fetchNextPage]);
  
  return (
    <div className="infinite-list">
      {data?.map(post => (
        <div key={post.id} className="post-item">
          <h3>{post.title}</h3>
          <p>{post.content}</p>
        </div>
      ))}
      
      <div ref={loadMoreRef} className="load-more">
        {isFetchingNextPage && <div>Loading more...</div>}
        {!hasNextPage && data?.length > 0 && <div>No more posts</div>}
      </div>
    </div>
  );
}

Table Export & Import

Export Functionality

Export table data to various formats with customizable options.

/**
 * Export table data to files
 * @param params - Export configuration
 * @returns Export function and state
 */
function useExport<TData = BaseRecord>(
  params?: UseExportConfig<TData>
): UseExportReturnType<TData>;

interface UseExportConfig<TData> {
  /** Resource name */
  resource?: string;
  /** Export format */
  format?: "csv" | "xlsx" | "json" | "pdf";
  /** Fields to export */
  fields?: Array<keyof TData>;
  /** Custom field mappers */
  mapData?: (data: TData[]) => any[];
  /** Export filters */
  filters?: CrudFilters;
  /** Export sorting */
  sorters?: CrudSorting;
  /** Maximum records to export */
  maxItemCount?: number;
  /** Custom filename */
  filename?: string;
}

interface UseExportReturnType<TData> {
  /** Trigger export function */
  triggerExport: () => Promise<void>;
  /** Whether export is in progress */
  isLoading: boolean;
  /** Export error if any */
  error?: Error;
}

Import Functionality

Import data from files with validation and error handling.

/**
 * Import data from files
 * @param params - Import configuration
 * @returns Import function and state
 */
function useImport<TData = BaseRecord>(
  params?: UseImportConfig<TData>
): UseImportReturnType<TData>;

interface UseImportConfig<TData> {
  /** Resource name */
  resource?: string;
  /** Supported file types */
  acceptedFileTypes?: string[];
  /** Custom data mapper */
  mapData?: (data: any[]) => TData[];
  /** Batch size for import */
  batchSize?: number;
  /** Validation function */
  validate?: (data: TData[]) => ValidationResult[];
}

interface UseImportReturnType<TData> {
  /** File input handler */
  inputProps: {
    type: "file";
    onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
    accept: string;
  };
  /** Whether import is in progress */
  isLoading: boolean;
  /** Import progress (0-100) */
  progress: number;
  /** Import errors */
  errors: ImportError[];
  /** Successful imports count */
  successCount: number;
}

interface ImportError {
  /** Row number with error */
  row: number;
  /** Error message */
  message: string;
  /** Field with error */
  field?: string;
}

interface ValidationResult {
  /** Whether validation passed */
  valid: boolean;
  /** Error messages */
  errors: string[];
  /** Row index */
  index: number;
}

Types

interface TableColumn<TData> {
  /** Column key */
  key: keyof TData;
  /** Column title */
  title: string;
  /** Whether column is sortable */
  sortable?: boolean;
  /** Whether column is filterable */
  filterable?: boolean;
  /** Column width */
  width?: string | number;
  /** Custom render function */
  render?: (value: any, record: TData, index: number) => React.ReactNode;
  /** Filter configuration */
  filterConfig?: {
    type: "text" | "select" | "date" | "number";
    options?: Array<{ label: string; value: any }>;
  };
}

interface TableAction<TData> {
  /** Action key */
  key: string;
  /** Action label */
  label: string;
  /** Action icon */
  icon?: React.ReactNode;
  /** Action handler */
  onClick: (record: TData) => void;
  /** Whether action is disabled */
  disabled?: (record: TData) => boolean;
  /** Action color/style */
  variant?: "primary" | "secondary" | "danger";
}

interface PaginationConfig {
  /** Current page */
  current: number;
  /** Page size */
  pageSize: number;
  /** Total items */
  total: number;
  /** Available page sizes */
  pageSizeOptions?: number[];
  /** Whether to show size changer */
  showSizeChanger?: boolean;
  /** Whether to show total */
  showTotal?: boolean;
  /** Custom total renderer */
  showTotalRenderer?: (total: number, range: [number, number]) => React.ReactNode;
}

interface SortConfig {
  /** Field to sort by */
  field: string;
  /** Sort direction */
  order: "asc" | "desc";
  /** Sort priority for multi-column sort */
  priority?: number;
}

interface FilterConfig {
  /** Field to filter */
  field: string;
  /** Filter operator */
  operator: CrudOperators;
  /** Filter value */
  value: any;
  /** Filter label for display */
  label?: string;
}

Install with Tessl CLI

npx tessl i tessl/npm-refinedev--core

docs

application-setup.md

authentication.md

data-operations.md

forms.md

index.md

navigation.md

tables-lists.md

utilities.md

tile.json