Advanced Usage
Multiple Tables on Same Page
When you have multiple data tables on the same page, you need to use urlKeys to avoid query parameter conflicts.
Example
import { DteProvider } from "mantine-datatable-extended";
function PageWithMultipleTables() {
return (
<Stack>
{/* Table 1 */}
<DteProvider
storeColumnsKey="users-table"
urlKeys={{
page: "users_page",
pageSize: "users_pageSize",
sorts: "users_sorts",
search: "users_search",
filters: "users_filters",
}}
columns={userColumns}
originalUseDataTableColumnsResult={userColumnsResult}
>
<DteExtended records={users} />
<DtePagination />
</DteProvider>
{/* Table 2 */}
<DteProvider
storeColumnsKey="products-table"
urlKeys={{
page: "products_page",
pageSize: "products_pageSize",
sorts: "products_sorts",
search: "products_search",
filters: "products_filters",
}}
columns={productColumns}
originalUseDataTableColumnsResult={productColumnsResult}
>
<DteExtended records={products} />
<DtePagination />
</DteProvider>
</Stack>
);
}Server-side with Multiple Tables
When using server-side, you also need to create a separate loader for each table:
const usersLoader = createDteLoader({
urlKeys: {
page: "users_page",
pageSize: "users_pageSize",
sorts: "users_sorts",
search: "users_search",
filters: "users_filters",
},
});
const productsLoader = createDteLoader({
urlKeys: {
page: "products_page",
pageSize: "products_pageSize",
sorts: "products_sorts",
search: "products_search",
filters: "products_filters",
},
});
export default async function Page({ searchParams }: Props) {
const usersParams = await usersLoader(searchParams);
const productsParams = await productsLoader(searchParams);
// Prefetch for both tables
// ...
}Custom URL Keys
You can customize the names of query parameters in the URL:
<DteProvider
urlKeys={{
page: "p",
pageSize: "ps",
sorts: "s",
search: "q",
filters: "f",
}}
// ...
/>Note: When customizing URL keys, you must use the same keys for both the server loader and client provider.
Custom Default Parameters
Set default values for parameters:
<DteProvider
defaultParams={{
page: 1,
pageSize: 20,
sorts: [
{ accessor: "createdAt", direction: "desc" },
{ accessor: "name", direction: "asc" },
],
search: {
accessors: ["name", "email"],
value: "",
},
filters: [
{
variant: "single_select",
accessor: "status",
value: "active",
},
],
}}
// ...
/>Programmatic Control
Use the useDteQueryParams hook to control parameters programmatically:
import { useDteQueryParams } from "mantine-datatable-extended";
function MyComponent() {
const {
page,
setPage,
pageSize,
setPageSize,
sorts,
setSorts,
search,
setSearch,
filters,
setFilters,
resetPage,
resetPageSize,
resetSorts,
resetSearch,
resetFilters,
resetAll,
} = useDteQueryParams();
const handleReset = () => {
resetAll(); // Reset all to default
};
const handleGoToPage = (newPage: number) => {
setPage(newPage);
};
const handleApplyFilter = () => {
setFilters([
...filters,
{
variant: "single_select",
accessor: "category",
value: "electronics",
},
]);
};
return (
<Group>
<Button onClick={handleReset}>Reset All</Button>
<Button onClick={() => handleGoToPage(1)}>Go to Page 1</Button>
<Button onClick={handleApplyFilter}>Apply Filter</Button>
</Group>
);
}Accessing Context
Use useDteContext to access context and utilities:
import { useDteContext } from "mantine-datatable-extended";
function MyComponent() {
const {
columns,
setTotalRecords,
setRecordsPerPageOptions,
paginationProps,
storeColumnsKey,
} = useDteContext();
// Use columns to render custom UI
const searchableColumns = columns.filter(
(col) => col.extend?.searchable
);
// Update total records when data changes
useEffect(() => {
setTotalRecords?.(totalRecords);
}, [totalRecords]);
// Custom records per page options
useEffect(() => {
setRecordsPerPageOptions?.([5, 10, 25, 50, 100]);
}, []);
}Custom Pagination Options
Change the records per page options:
function MyTable() {
const { setRecordsPerPageOptions } = useDteContext();
useEffect(() => {
setRecordsPerPageOptions([5, 10, 25, 50, 100, 200]);
}, []);
return <DtePagination recordsPerPageOptions={[5, 10, 25, 50, 100]} />;
}Conditional Rendering
Render components based on conditions:
function ConditionalDataTable() {
const { filters } = useDteQueryParams();
const { columns } = useDteContext();
const hasFilterableColumns = columns.some(
(col) => col.extend?.filterable
);
return (
<>
{hasFilterableColumns && <DteFilter />}
<DteExtended records={records} />
{filters.length > 0 && (
<Text size="sm" c="dimmed">
{filters.length} filter(s) applied
</Text>
)}
</>
);
}Custom i18n
Pass i18n to DteProvider to customize display text. Grouped by component - only override the keys you need:
function InternationalizedTable() {
return (
<DteProvider
{...props}
i18n={{
search: { search: "Search..." },
filter: { resetFilter: "Clear Filters" },
sort: {
sort: "Sort",
addSort: "Add Sort",
resetSort: "Reset",
asc: "Ascending",
desc: "Descending",
},
view: { columnsToggle: "Columns" },
pagination: {
rowsPerPage: "Rows per page",
pageOfTotalPages: ["Page", "of"],
startEndRecordOfTotalRecords: ["Showing", "-", "of"],
},
}}
>
<Group>
<DteSearch />
<DteFilter />
</Group>
<DteSortList />
<DteColumnsToggle />
<DtePagination />
</DteProvider>
);
}Integrating with Forms
Combine with forms to export/import filters:
import { useDteQueryParams } from "mantine-datatable-extended";
function ExportFiltersButton() {
const { filters, sorts, search } = useDteQueryParams();
const handleExport = () => {
const config = {
filters,
sorts,
search,
};
const blob = new Blob([JSON.stringify(config, null, 2)], {
type: "application/json",
});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "table-config.json";
a.click();
};
return <Button onClick={handleExport}>Export Config</Button>;
}
function ImportFiltersButton() {
const { setFilters, setSorts, setSearch } = useDteQueryParams();
const handleImport = async (event: React.ChangeEvent<HTMLInputElement>) => {
const file = event.target.files?.[0];
if (!file) return;
const text = await file.text();
const config = JSON.parse(text);
setFilters(config.filters || []);
setSorts(config.sorts || []);
setSearch(config.search || { accessors: [], value: "" });
};
return (
<input
type="file"
accept="application/json"
onChange={handleImport}
/>
);
}Type Safety
The library has full TypeScript support. Use generic types for type safety:
type User = {
id: string;
name: string;
email: string;
role: "admin" | "user" | "guest";
};
const columns: TDteExtendedColumnProps<User>[] = [
{
accessor: "name", // Type-safe
title: "Name",
extend: {
searchable: true,
},
},
{
accessor: "role", // Type-safe with union type
title: "Role",
extend: {
filterable: true,
filterVariant: "single_select",
filterOptions: {
data: [
{ label: "Admin", value: "admin" },
{ label: "User", value: "user" },
{ label: "Guest", value: "guest" },
],
},
},
},
];
// Type-safe in DteProvider
<DteProvider<User>
columns={columns}
// ...
>
<DteExtended<User> records={users} />
</DteProvider>