mirror of
https://github.com/nextui-org/nextui.git
synced 2025-12-08 19:26:11 +00:00
fix(docs): table async pagination (#1491)
* fix(docs): change to swr * chore(table): change to swr * fix(table): keep the pagination when switching pages * chore(repo): pnpm lockfile updated
This commit is contained in:
parent
64571e468c
commit
d8d2b87cb8
@ -11,8 +11,8 @@ import {
|
||||
Spinner,
|
||||
Pagination,
|
||||
} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
import {useCallback, useMemo, useState} from "react";
|
||||
import {useMemo, useState} from "react";
|
||||
import useSWR from "swr";
|
||||
|
||||
type SWCharacter = {
|
||||
name: string;
|
||||
@ -21,50 +21,23 @@ type SWCharacter = {
|
||||
birth_year: string;
|
||||
};
|
||||
|
||||
const fetcher = (...args: Parameters<typeof fetch>) => fetch(...args).then((res) => res.json());
|
||||
|
||||
export default function Page() {
|
||||
const [page, setPage] = useState(1);
|
||||
const [total, setTotal] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
|
||||
const {data, isLoading} = useSWR<{
|
||||
count: number;
|
||||
results: SWCharacter[];
|
||||
}>(`https://swapi.py4e.com/api/people?page=${page}`, fetcher, {
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const rowsPerPage = 10;
|
||||
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, cursor}) {
|
||||
// If no cursor is available, then we're loading the first page.
|
||||
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
|
||||
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
|
||||
let json = await res.json();
|
||||
|
||||
setTotal(json.count);
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
cursor: json.next,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const pages = Math.ceil(total / rowsPerPage);
|
||||
|
||||
const items = useMemo(() => {
|
||||
const start = (page - 1) * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
|
||||
return list.items.slice(start, end);
|
||||
}, [page, list.items?.length]);
|
||||
|
||||
const onPaginationChange = useCallback(
|
||||
(page: number) => {
|
||||
setIsLoading(true);
|
||||
if (page >= list.items.length / rowsPerPage) {
|
||||
list.loadMore();
|
||||
}
|
||||
setPage(page);
|
||||
},
|
||||
[list.items.length],
|
||||
);
|
||||
const pages = useMemo(() => {
|
||||
return data?.count ? Math.ceil(data.count / rowsPerPage) : 0;
|
||||
}, [data?.count, rowsPerPage]);
|
||||
|
||||
return (
|
||||
<div className="p-6">
|
||||
@ -80,7 +53,7 @@ export default function Page() {
|
||||
color="primary"
|
||||
page={page}
|
||||
total={pages}
|
||||
onChange={onPaginationChange}
|
||||
onChange={(page) => setPage(page)}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
@ -96,8 +69,8 @@ export default function Page() {
|
||||
<TableColumn key="birth_year">Birth year</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
isLoading={isLoading && !items.length}
|
||||
items={items}
|
||||
isLoading={isLoading || data?.results.length === 0}
|
||||
items={data?.results ?? []}
|
||||
loadingContent={<Spinner />}
|
||||
>
|
||||
{(item) => (
|
||||
|
||||
@ -256,7 +256,7 @@ const users = [
|
||||
},
|
||||
];
|
||||
|
||||
type User = (typeof users)[0];
|
||||
type User = typeof users[0];
|
||||
|
||||
export default function Page() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
@ -256,7 +256,7 @@ const users = [
|
||||
},
|
||||
];
|
||||
|
||||
type User = (typeof users)[0];
|
||||
type User = typeof users[0];
|
||||
|
||||
export default function Page() {
|
||||
const [filterValue, setFilterValue] = useState("");
|
||||
|
||||
@ -108,9 +108,9 @@ function CodeTypewriter({value, className, css, ...props}: any) {
|
||||
return (
|
||||
<Pre className={className} css={css} {...props}>
|
||||
<code
|
||||
dangerouslySetInnerHTML={{__html: value}}
|
||||
ref={wrapperRef}
|
||||
className={className}
|
||||
dangerouslySetInnerHTML={{__html: value}}
|
||||
style={{opacity: 0}}
|
||||
/>
|
||||
</Pre>
|
||||
@ -155,7 +155,7 @@ const CodeBlock = React.forwardRef<HTMLPreElement, CodeBlockProps>((_props, forw
|
||||
{...props}
|
||||
>
|
||||
{showWindowIcons && <WindowActions title={title} />}
|
||||
<code dangerouslySetInnerHTML={{__html: result}} className={clsx(classes, codeClasses)} />
|
||||
<code className={clsx(classes, codeClasses)} dangerouslySetInnerHTML={{__html: result}} />
|
||||
</Pre>
|
||||
);
|
||||
});
|
||||
|
||||
@ -1,50 +1,22 @@
|
||||
const App = `import {Table, TableHeader, TableColumn, TableBody, TableRow, TableCell, Pagination, Spinner, getKeyValue} from "@nextui-org/react";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
import useSWR from "swr";
|
||||
|
||||
const fetcher = (...args) => fetch(...args).then((res) => res.json());
|
||||
|
||||
export default function App() {
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [total, setTotal] = React.useState(0);
|
||||
const [isLoading, setIsLoading] = React.useState(true);
|
||||
|
||||
const {data, isLoading} = useSWR(\`https://swapi.py4e.com/api/people?page=\$\{page\}\`, fetcher, {
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const rowsPerPage = 10;
|
||||
|
||||
let list = useAsyncList({
|
||||
async load({signal, cursor}) {
|
||||
// If no cursor is available, then we're loading the first page.
|
||||
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
|
||||
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
|
||||
let json = await res.json();
|
||||
const pages = useMemo(() => {
|
||||
return data?.count ? Math.ceil(data.count / rowsPerPage) : 0;
|
||||
}, [data?.count, rowsPerPage]);
|
||||
|
||||
setTotal(json.count);
|
||||
|
||||
setIsLoading(false);
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
cursor: json.next,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const pages = Math.ceil(total / rowsPerPage);
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
const start = (page - 1) * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
|
||||
return list.items.slice(start, end);
|
||||
}, [page, list.items?.length]);
|
||||
|
||||
const onPaginationChange = React.useCallback(
|
||||
(page) => {
|
||||
setIsLoading(true);
|
||||
if (page >= list.items.length / rowsPerPage) {
|
||||
list.loadMore();
|
||||
}
|
||||
setPage(page);
|
||||
},
|
||||
[list.items.length],
|
||||
);
|
||||
const loadingState = isLoading || data?.results.length === 0 ? "loading" : "idle";
|
||||
|
||||
return (
|
||||
<Table
|
||||
@ -59,14 +31,12 @@ export default function App() {
|
||||
color="primary"
|
||||
page={page}
|
||||
total={pages}
|
||||
onChange={onPaginationChange}
|
||||
onChange={(page) => setPage(page)}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
}
|
||||
classNames={{
|
||||
table: "min-h-[400px]",
|
||||
}}
|
||||
{...args}
|
||||
>
|
||||
<TableHeader>
|
||||
<TableColumn key="name">Name</TableColumn>
|
||||
@ -75,12 +45,12 @@ export default function App() {
|
||||
<TableColumn key="birth_year">Birth year</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody
|
||||
isLoading={isLoading && !items.length}
|
||||
items={items}
|
||||
items={data?.results ?? []}
|
||||
loadingContent={<Spinner />}
|
||||
loadingState={loadingState}
|
||||
>
|
||||
{(item) => (
|
||||
<TableRow key={item.name}>
|
||||
<TableRow key={item?.name}>
|
||||
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
@ -257,8 +257,7 @@ You can use the [Pagination](/components/pagination) component to paginate the t
|
||||
|
||||
### Async Pagination
|
||||
|
||||
It is also possible to use the [Pagination](/components/pagination) component to paginate the table asynchronously. To fetch the data, we are using the `useAsyncList` hook from [@react-stately/data](https://react-spectrum.adobe.com/react-stately/useAsyncList.html).
|
||||
Please check the installation instructions in the [Sorting Rows](#sorting-rows) section.
|
||||
It is also possible to use the [Pagination](/components/pagination) component to paginate the table asynchronously. To fetch the data, we are using the `useSWR` hook from [SWR](https://swr.vercel.app/docs/pagination).
|
||||
|
||||
<CodeDemo
|
||||
asIframe
|
||||
|
||||
@ -18,17 +18,17 @@
|
||||
"@codesandbox/sandpack-react": "^2.6.4",
|
||||
"@mapbox/rehype-prism": "^0.6.0",
|
||||
"@nextui-org/aria-utils": "workspace:*",
|
||||
"@nextui-org/badge": "workspace:*",
|
||||
"@nextui-org/code": "workspace:*",
|
||||
"@nextui-org/divider": "workspace:*",
|
||||
"@nextui-org/kbd": "workspace:*",
|
||||
"@nextui-org/react": "workspace:*",
|
||||
"@nextui-org/shared-icons": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/spacer": "workspace:*",
|
||||
"@nextui-org/kbd": "workspace:*",
|
||||
"@nextui-org/code": "workspace:*",
|
||||
"@nextui-org/badge": "workspace:*",
|
||||
"@nextui-org/skeleton": "workspace:*",
|
||||
"@nextui-org/spacer": "workspace:*",
|
||||
"@nextui-org/spinner": "workspace:*",
|
||||
"@nextui-org/divider": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
"@nextui-org/use-clipboard": "workspace:*",
|
||||
"@nextui-org/use-infinite-scroll": "workspace:*",
|
||||
"@nextui-org/use-is-mobile": "workspace:*",
|
||||
@ -84,6 +84,7 @@
|
||||
"scroll-into-view-if-needed": "3.0.10",
|
||||
"sharp": "^0.32.1",
|
||||
"shelljs": "^0.8.4",
|
||||
"swr": "^2.2.1",
|
||||
"tailwind-variants": "^0.1.13",
|
||||
"unified": "^9.2.2",
|
||||
"unist-util-visit": "^4.1.2",
|
||||
|
||||
@ -319,7 +319,7 @@ const PrimaryActionTemplate = (args: CardProps) => {
|
||||
},
|
||||
];
|
||||
|
||||
type ListItem = (typeof list)[number];
|
||||
type ListItem = typeof list[number];
|
||||
|
||||
const handlePress = (item: ListItem) => {
|
||||
// eslint-disable-next-line no-console
|
||||
|
||||
@ -38,9 +38,9 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@nextui-org/checkbox": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/react-utils": "workspace:*",
|
||||
"@nextui-org/shared-icons": "workspace:*",
|
||||
"@nextui-org/shared-utils": "workspace:*",
|
||||
"@nextui-org/spacer": "workspace:*",
|
||||
"@nextui-org/system": "workspace:*",
|
||||
"@nextui-org/theme": "workspace:*",
|
||||
@ -49,22 +49,23 @@
|
||||
"@react-aria/table": "^3.11.0",
|
||||
"@react-aria/utils": "^3.19.0",
|
||||
"@react-aria/visually-hidden": "^3.8.3",
|
||||
"@react-stately/virtualizer": "^3.6.0",
|
||||
"@react-stately/table": "^3.11.0",
|
||||
"@react-stately/virtualizer": "^3.6.0",
|
||||
"@react-types/grid": "^3.2.0",
|
||||
"@react-types/table": "^3.8.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nextui-org/chip": "workspace:*",
|
||||
"@nextui-org/button": "workspace:*",
|
||||
"@nextui-org/spinner": "workspace:*",
|
||||
"@nextui-org/chip": "workspace:*",
|
||||
"@nextui-org/pagination": "workspace:*",
|
||||
"@nextui-org/use-infinite-scroll": "workspace:*",
|
||||
"@nextui-org/spinner": "workspace:*",
|
||||
"@nextui-org/tooltip": "workspace:*",
|
||||
"@nextui-org/use-infinite-scroll": "workspace:*",
|
||||
"@nextui-org/user": "workspace:*",
|
||||
"@react-stately/data": "^3.10.1",
|
||||
"clean-package": "2.2.0",
|
||||
"react": "^18.0.0"
|
||||
"react": "^18.0.0",
|
||||
"swr": "^2.2.1"
|
||||
},
|
||||
"clean-package": "../../../clean-package.config.json"
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React from "react";
|
||||
import React, {useMemo} from "react";
|
||||
import {Meta} from "@storybook/react";
|
||||
import {table} from "@nextui-org/theme";
|
||||
import {User} from "@nextui-org/user";
|
||||
@ -10,6 +10,7 @@ import {Tooltip} from "@nextui-org/tooltip";
|
||||
import {EditIcon, DeleteIcon, EyeIcon} from "@nextui-org/shared-icons";
|
||||
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
|
||||
import {useAsyncList} from "@react-stately/data";
|
||||
import useSWR from "swr";
|
||||
|
||||
import {
|
||||
Table,
|
||||
@ -236,7 +237,7 @@ const CustomCellTemplate = (args: TableProps) => {
|
||||
},
|
||||
];
|
||||
|
||||
type User = (typeof users)[0];
|
||||
type User = typeof users[0];
|
||||
|
||||
const statusColorMap: Record<string, ChipProps["color"]> = {
|
||||
active: "success",
|
||||
@ -376,7 +377,7 @@ const CustomCellWithClassnamesTemplate = (args: TableProps) => {
|
||||
},
|
||||
];
|
||||
|
||||
type User = (typeof users)[0];
|
||||
type User = typeof users[0];
|
||||
|
||||
const statusColorMap: Record<string, ChipProps["color"]> = {
|
||||
active: "success",
|
||||
@ -758,38 +759,25 @@ const PaginatedTemplate = (args: TableProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
const fetcher = (...args: Parameters<typeof fetch>) => fetch(...args).then((res) => res.json());
|
||||
|
||||
const AsyncPaginatedTemplate = (args: TableProps) => {
|
||||
const [page, setPage] = React.useState(1);
|
||||
const [total, setTotal] = React.useState(0);
|
||||
|
||||
const {data, isLoading} = useSWR<{
|
||||
count: number;
|
||||
results: SWCharacter[];
|
||||
}>(`https://swapi.py4e.com/api/people?page=${page}`, fetcher, {
|
||||
keepPreviousData: true,
|
||||
});
|
||||
|
||||
const rowsPerPage = 10;
|
||||
|
||||
let list = useAsyncList<SWCharacter>({
|
||||
async load({signal, cursor}) {
|
||||
// If no cursor is available, then we're loading the first page.
|
||||
// Otherwise, the cursor is the next URL to load, as returned from the previous page.
|
||||
const res = await fetch(cursor || "https://swapi.py4e.com/api/people/?search=", {signal});
|
||||
let json = await res.json();
|
||||
const pages = useMemo(() => {
|
||||
return data?.count ? Math.ceil(data?.count / rowsPerPage) : 0;
|
||||
}, [data?.count, rowsPerPage]);
|
||||
|
||||
setTotal(json.count);
|
||||
|
||||
return {
|
||||
items: json.results,
|
||||
cursor: json.next,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
const pages = Math.ceil(total / rowsPerPage);
|
||||
|
||||
const items = React.useMemo(() => {
|
||||
const start = (page - 1) * rowsPerPage;
|
||||
const end = start + rowsPerPage;
|
||||
|
||||
return list.items.slice(start, end);
|
||||
}, [page, list.items?.length, list.loadingState]);
|
||||
|
||||
const loadingState = items.length === 0 ? "loading" : list.loadingState;
|
||||
const loadingState = isLoading || data?.results.length === 0 ? "loading" : "idle";
|
||||
|
||||
return (
|
||||
<Table
|
||||
@ -804,12 +792,7 @@ const AsyncPaginatedTemplate = (args: TableProps) => {
|
||||
color="primary"
|
||||
page={page}
|
||||
total={pages}
|
||||
onChange={(page) => {
|
||||
if (page >= list.items.length / rowsPerPage) {
|
||||
list.loadMore();
|
||||
}
|
||||
setPage(page);
|
||||
}}
|
||||
onChange={(page) => setPage(page)}
|
||||
/>
|
||||
</div>
|
||||
) : null
|
||||
@ -822,9 +805,13 @@ const AsyncPaginatedTemplate = (args: TableProps) => {
|
||||
<TableColumn key="mass">Mass</TableColumn>
|
||||
<TableColumn key="birth_year">Birth year</TableColumn>
|
||||
</TableHeader>
|
||||
<TableBody items={items} loadingContent={<Spinner />} loadingState={loadingState}>
|
||||
<TableBody
|
||||
items={data?.results ?? []}
|
||||
loadingContent={<Spinner />}
|
||||
loadingState={loadingState}
|
||||
>
|
||||
{(item) => (
|
||||
<TableRow key={item.name}>
|
||||
<TableRow key={item?.name}>
|
||||
{(columnKey) => <TableCell>{getKeyValue(item, columnKey)}</TableCell>}
|
||||
</TableRow>
|
||||
)}
|
||||
|
||||
@ -65,7 +65,7 @@ export const spacingScaleKeys = [
|
||||
|
||||
export const mappedSpacingScaleKeys = spacingScaleKeys.map((key) => `unit-${key}`);
|
||||
|
||||
export type SpacingScaleKeys = (typeof spacingScaleKeys)[number];
|
||||
export type SpacingScaleKeys = typeof spacingScaleKeys[number];
|
||||
|
||||
export type SpacingScale = Partial<Record<SpacingScaleKeys, string>>;
|
||||
|
||||
|
||||
16
pnpm-lock.yaml
generated
16
pnpm-lock.yaml
generated
@ -471,6 +471,9 @@ importers:
|
||||
shelljs:
|
||||
specifier: ^0.8.4
|
||||
version: 0.8.5
|
||||
swr:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(react@18.2.0)
|
||||
tailwind-variants:
|
||||
specifier: ^0.1.13
|
||||
version: 0.1.13(tailwindcss@3.3.3)
|
||||
@ -2009,6 +2012,9 @@ importers:
|
||||
react:
|
||||
specifier: ^18.2.0
|
||||
version: 18.2.0
|
||||
swr:
|
||||
specifier: ^2.2.1
|
||||
version: 2.2.1(react@18.2.0)
|
||||
|
||||
packages/components/tabs:
|
||||
dependencies:
|
||||
@ -22956,6 +22962,15 @@ packages:
|
||||
stable: 0.1.8
|
||||
dev: true
|
||||
|
||||
/swr@2.2.1(react@18.2.0):
|
||||
resolution: {integrity: sha512-KJVA7dGtOBeZ+2sycEuzUfVIP5lZ/cd0xjevv85n2YG0x1uHJQicjAtahVZL6xG3+TjqhbBqimwYzVo3saeVXQ==}
|
||||
peerDependencies:
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
client-only: 0.0.1
|
||||
react: 18.2.0
|
||||
use-sync-external-store: 1.2.0(react@18.2.0)
|
||||
|
||||
/symbol-tree@3.2.4:
|
||||
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
|
||||
dev: true
|
||||
@ -24036,7 +24051,6 @@ packages:
|
||||
react: ^18.2.0
|
||||
dependencies:
|
||||
react: 18.2.0
|
||||
dev: false
|
||||
|
||||
/use@3.1.1:
|
||||
resolution: {integrity: sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user