mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-20 00:11:50 +00:00
fix(ui): keep controlled client-side table pages across data reloads
Controlled client-mode DataTables no longer let TanStack reset the page index when rows change, since the owner of the pagination state decides the page. Once rows settle, a page past the end snaps back to the last page, matching server mode
This commit is contained in:
parent
eda81fff59
commit
4d30bbce45
2 changed files with 104 additions and 3 deletions
|
|
@ -357,6 +357,79 @@ describe("DataTable pagination", () => {
|
|||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
type ClientPageHarnessProps = {
|
||||
data: Person[];
|
||||
isLoading?: boolean;
|
||||
initialPageIndex: number;
|
||||
onChange: (next: PaginationState) => void;
|
||||
};
|
||||
|
||||
function ClientPageHarness({ data, isLoading = false, initialPageIndex, onChange }: ClientPageHarnessProps) {
|
||||
const [pagination, setPagination] = useState<PaginationState>({ pageIndex: initialPageIndex, pageSize: 2 });
|
||||
const handleChange: OnChangeFn<PaginationState> = (updater) => {
|
||||
const next = typeof updater === "function" ? updater(pagination) : updater;
|
||||
onChange(next);
|
||||
setPagination(next);
|
||||
};
|
||||
return (
|
||||
<DataTable
|
||||
data={data}
|
||||
columns={nameCellColumns}
|
||||
paginationMode="client"
|
||||
pageSizeOptions={[2]}
|
||||
pagination={pagination}
|
||||
onPaginationChange={handleChange}
|
||||
isLoading={isLoading}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
it("client mode keeps a controlled page when rows arrive after loading and when they are refetched", async () => {
|
||||
const onChange = vi.fn();
|
||||
const { rerender } = render(<ClientPageHarness data={[]} isLoading initialPageIndex={1} onChange={onChange} />);
|
||||
|
||||
rerender(<ClientPageHarness data={fivePeople} initialPageIndex={1} onChange={onChange} />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
expect(names()).toEqual(["P2", "P3"]);
|
||||
|
||||
rerender(<ClientPageHarness data={[...fivePeople]} initialPageIndex={1} onChange={onChange} />);
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
expect(names()).toEqual(["P2", "P3"]);
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("client mode snaps a controlled page past the end back to the last page", async () => {
|
||||
const onChange = vi.fn();
|
||||
render(<ClientPageHarness data={fivePeople} initialPageIndex={5} onChange={onChange} />);
|
||||
|
||||
await waitFor(() => expect(onChange).toHaveBeenCalledWith({ pageIndex: 2, pageSize: 2 }));
|
||||
expect(onChange).toHaveBeenCalledTimes(1);
|
||||
expect(names()).toEqual(["P4"]);
|
||||
});
|
||||
|
||||
it("client mode leaves a controlled page alone while there are no rows to page through", async () => {
|
||||
const onChange = vi.fn();
|
||||
render(<ClientPageHarness data={[]} initialPageIndex={3} onChange={onChange} />);
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 20));
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("client mode without a controlled page still returns to the first page when the rows change", async () => {
|
||||
const user = userEvent.setup();
|
||||
const { rerender } = render(
|
||||
<DataTable data={fivePeople} columns={nameCellColumns} paginationMode="client" pageSizeOptions={[2]} />,
|
||||
);
|
||||
|
||||
await user.click(screen.getByTestId("pagination-next"));
|
||||
expect(names()).toEqual(["P2", "P3"]);
|
||||
|
||||
rerender(
|
||||
<DataTable data={[...fivePeople]} columns={nameCellColumns} paginationMode="client" pageSizeOptions={[2]} />,
|
||||
);
|
||||
await waitFor(() => expect(names()).toEqual(["P0", "P1"]));
|
||||
});
|
||||
|
||||
it("server mode resumes clamping once the error clears and a real rowCount arrives", async () => {
|
||||
const onChange = vi.fn();
|
||||
const { rerender } = render(<ServerPageHarness rowCount={0} isError initialPageIndex={2} onChange={onChange} />);
|
||||
|
|
|
|||
|
|
@ -425,7 +425,7 @@ function useControllable<T>(
|
|||
return { value: internal, onChange: setInternal };
|
||||
}
|
||||
|
||||
function useServerPageClamp(
|
||||
function usePageClamp(
|
||||
active: boolean,
|
||||
rowCount: number | undefined,
|
||||
pagination: { value: PaginationState; onChange: OnChangeFn<PaginationState> },
|
||||
|
|
@ -484,7 +484,6 @@ function useDataTableInstance<TData extends RowData, TValue>(
|
|||
pageIndex: 0,
|
||||
pageSize: pageSizeOptions[0] ?? 25,
|
||||
});
|
||||
useServerPageClamp(paginationMode === "server" && !isLoading && !isError, rowCount, paginationState);
|
||||
const filterState = useControllable<ColumnFiltersState>(
|
||||
columnFilters,
|
||||
onColumnFiltersChange,
|
||||
|
|
@ -536,9 +535,38 @@ function useDataTableInstance<TData extends RowData, TValue>(
|
|||
...(getRowId !== undefined ? { getRowId } : {}),
|
||||
...(enableRowSelection !== undefined ? { enableRowSelection } : {}),
|
||||
...(paginationMode === "server" && rowCount !== undefined ? { rowCount } : {}),
|
||||
autoResetPageIndex: pagination === undefined && paginationMode !== "server",
|
||||
};
|
||||
|
||||
return useReactTable(tableOptions);
|
||||
const table = useReactTable(tableOptions);
|
||||
const clampOptions: SettledPageClampOptions = {
|
||||
paginationMode,
|
||||
controlled: pagination !== undefined,
|
||||
settled: !isLoading && !isError,
|
||||
rowCount,
|
||||
pagination: paginationState,
|
||||
};
|
||||
useSettledPageClamp(table, clampOptions);
|
||||
return table;
|
||||
}
|
||||
|
||||
type SettledPageClampOptions = {
|
||||
paginationMode: PaginationMode;
|
||||
controlled: boolean;
|
||||
settled: boolean;
|
||||
rowCount: number | undefined;
|
||||
pagination: { value: PaginationState; onChange: OnChangeFn<PaginationState> };
|
||||
};
|
||||
|
||||
function useSettledPageClamp<TData extends RowData>(table: Table<TData>, options: SettledPageClampOptions): void {
|
||||
const { paginationMode, controlled, settled, rowCount, pagination } = options;
|
||||
const clientRowCount = paginationMode === "client" ? table.getPrePaginationRowModel().rows.length : 0;
|
||||
const clientPageIsClampable = paginationMode === "client" && controlled && clientRowCount > 0;
|
||||
usePageClamp(
|
||||
settled && (paginationMode === "server" || clientPageIsClampable),
|
||||
paginationMode === "server" ? rowCount : clientRowCount,
|
||||
pagination,
|
||||
);
|
||||
}
|
||||
|
||||
export function DataTable<TData extends RowData, TValue>(props: DataTableProps<TData, TValue>) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue