React meta-framework for building enterprise CRUD applications with authentication, data management, and headless UI integration
—
Comprehensive table functionality with sorting, filtering, pagination, search, and URL synchronization for data-heavy interfaces.
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" ? "↑" : "↓";
}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>
);
}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>
);
}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 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;
}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