Skip to content

Commit 908aed4

Browse files
authored
Merge pull request #101 from mckervinc/feature/infinite-scroll
Feature: Infinite Scroll
2 parents 43102fb + 8a2eed6 commit 908aed4

14 files changed

Lines changed: 1842 additions & 841 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# CHANGELOG
22

3+
## 1.5.0
4+
5+
_2025-06-11_
6+
7+
### Features
8+
9+
- added `onLoadRows` prop, which enables infinite loading
10+
- added `endComponent` prop, which enables a component to be displayed at the end of the table
11+
312
## 1.4.3
413

514
_2025-05-09_

example/package.json

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -18,53 +18,53 @@
1818
"@fortawesome/free-regular-svg-icons": "^6.7.2",
1919
"@fortawesome/free-solid-svg-icons": "^6.7.2",
2020
"@fortawesome/react-fontawesome": "^0.2.2",
21-
"@ngneat/falso": "^7.3.0",
22-
"@radix-ui/react-accordion": "^1.2.7",
23-
"@radix-ui/react-checkbox": "^1.2.2",
24-
"@radix-ui/react-label": "^2.1.4",
25-
"@radix-ui/react-popover": "^1.1.10",
26-
"@radix-ui/react-radio-group": "^1.3.2",
27-
"@radix-ui/react-slot": "^1.2.0",
28-
"@radix-ui/react-switch": "^1.2.2",
21+
"@ngneat/falso": "^7.4.0",
22+
"@radix-ui/react-accordion": "^1.2.11",
23+
"@radix-ui/react-checkbox": "^1.3.2",
24+
"@radix-ui/react-label": "^2.1.7",
25+
"@radix-ui/react-popover": "^1.1.14",
26+
"@radix-ui/react-radio-group": "^1.3.7",
27+
"@radix-ui/react-slot": "^1.2.3",
28+
"@radix-ui/react-switch": "^1.2.5",
2929
"class-variance-authority": "^0.7.1",
3030
"clsx": "^2.1.1",
31-
"country-flag-icons": "^1.5.18",
31+
"country-flag-icons": "^1.5.19",
3232
"lodash": "^4.17.21",
33-
"lucide-react": "^0.501.0",
33+
"lucide-react": "^0.514.0",
3434
"react": "^19.1.0",
3535
"react-dom": "^19.1.0",
3636
"react-fluid-table": "link:..",
37-
"react-router-dom": "^7.5.1",
37+
"react-router-dom": "^7.6.2",
3838
"react-syntax-highlighter": "^15.6.1",
3939
"tailwind-merge": "^2.5.5",
4040
"tailwindcss-animate": "^1.0.7"
4141
},
4242
"devDependencies": {
43-
"@eslint/compat": "^1.2.8",
43+
"@eslint/compat": "^1.2.9",
4444
"@eslint/eslintrc": "^3.3.1",
45-
"@eslint/js": "^9.25.0",
46-
"@types/lodash": "^4.17.16",
45+
"@eslint/js": "^9.28.0",
46+
"@types/lodash": "^4.17.17",
4747
"@types/node": "^20",
4848
"@types/react": "^19",
4949
"@types/react-dom": "^19",
5050
"@types/react-syntax-highlighter": "^15.5.13",
51-
"@typescript-eslint/eslint-plugin": "^8.30.1",
52-
"@typescript-eslint/parser": "^8.30.1",
53-
"@vitejs/plugin-react": "^4.4.0",
51+
"@typescript-eslint/eslint-plugin": "^8.34.0",
52+
"@typescript-eslint/parser": "^8.34.0",
53+
"@vitejs/plugin-react": "^4.5.2",
5454
"autoprefixer": "^10.4.21",
55-
"eslint": "9.24.0",
55+
"eslint": "9.28.0",
5656
"eslint-config-prettier": "^10.1.2",
5757
"eslint-plugin-prettier": "^5.2.6",
5858
"eslint-plugin-react-hooks": "^5.2.0",
59-
"eslint-plugin-react-refresh": "^0.4.19",
60-
"globals": "^16.0.0",
59+
"eslint-plugin-react-refresh": "^0.4.20",
60+
"globals": "^16.2.0",
6161
"postcss": "^8.5.3",
6262
"prettier": "^3.5.3",
6363
"prettier-plugin-tailwindcss": "^0.6.11",
6464
"tailwindcss": "^3.4.17",
6565
"typescript": "^5.8.3",
66-
"typescript-eslint": "^8.30.1",
67-
"vite": "^6.3.2"
66+
"typescript-eslint": "^8.34.0",
67+
"vite": "^6.3.5"
6868
},
6969
"volta": {
7070
"node": "20.12.1",

example/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import { Example9 } from "./examples/09-scroll";
1313
import { Example10 } from "./examples/10-footer";
1414
import { Example11 } from "./examples/11-heights";
1515
import { Example12 } from "./examples/12-frozen";
16+
import { Example13 } from "./examples/13-infinite";
1617
import Layout from "./components/Layout";
1718

1819
const router = createHashRouter([
@@ -67,6 +68,10 @@ const router = createHashRouter([
6768
path: "/frozen",
6869
element: <Example12 />
6970
},
71+
{
72+
path: "/infinite",
73+
element: <Example13 />
74+
},
7075
{
7176
path: "/props",
7277
element: <Props />

example/src/Props.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,24 @@ const data: PropData[] = [
209209
type: "boolean",
210210
description: "Controls whether or not the footer is sticky. This does nothing if footerComponent is not specified.",
211211
default: "false"
212+
},
213+
{
214+
prop: "endComponent",
215+
type: "(props: { isLoading: boolean; hasMoreData: boolean; }) => Element",
216+
description: "You can provide an optional component that is rendered at the end of the table."
217+
},
218+
{
219+
prop: "onLoadRows",
220+
type: "() => Promise<boolean>",
221+
description:
222+
"This will load more data to the table. This enables infinite scrolling. If the functions returns true, it asssumes more data is available. Returning false means no more data is available."
223+
},
224+
{
225+
prop: "asyncOverscan",
226+
type: "number",
227+
description:
228+
"The number of rows from the bottom of the table that need to be rendered before onLoadRows is triggered.",
229+
default: "1"
212230
}
213231
];
214232

example/src/components/Layout.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ const links = (
5151
<Link to="/frozen" className="block px-4 py-3 text-[#ffffffe6] hover:bg-[rgba(255,255,255,.08)] hover:text-white">
5252
Frozen
5353
</Link>
54+
<Link to="/infinite" className="block px-4 py-3 text-[#ffffffe6] hover:bg-[rgba(255,255,255,.08)] hover:text-white">
55+
Infinite
56+
</Link>
5457
<Link to="/props" className="block px-4 py-3 text-[#ffffffe6] hover:bg-[rgba(255,255,255,.08)] hover:text-white">
5558
Table Props
5659
</Link>

example/src/data.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,4 +46,22 @@ const testData: TestData[] = _.range(3000).map(i => ({
4646
zipCode: randZipCode()
4747
}));
4848

49-
export { testData, type TestData };
49+
const generateTestData = (rows: number) => {
50+
return _.range(rows).map(i => ({
51+
id: i + 1,
52+
firstName: randFirstName(),
53+
lastName: randLastName(),
54+
email: randEmail(),
55+
avatar: randImg({ width: 134, height: 134 }).replace("random=", "?random="),
56+
country: randCountryCode().toLowerCase(),
57+
words: randCatchPhrase(),
58+
sentence: randSentence(),
59+
lorem: randParagraph(),
60+
address: randStreetAddress(),
61+
city: randCity(),
62+
state: randStateAbbr(),
63+
zipCode: randZipCode()
64+
}));
65+
};
66+
67+
export { generateTestData, testData, type TestData };
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import { ColumnProps, Table } from "react-fluid-table";
2+
import { generateTestData, TestData } from "../data";
3+
import { useSource, useTitle } from "@/hooks/useTitle";
4+
import { useCallback, useState } from "react";
5+
6+
const columns: ColumnProps<TestData>[] = [
7+
{
8+
key: "id",
9+
header: "ID",
10+
width: 50
11+
},
12+
{
13+
key: "firstName",
14+
header: "First",
15+
width: 120
16+
},
17+
{
18+
key: "lastName",
19+
header: "Last",
20+
width: 120
21+
},
22+
{
23+
key: "email",
24+
header: "Email",
25+
width: 250
26+
}
27+
];
28+
29+
const Source = `
30+
type TestData = {
31+
id: number;
32+
firstName: string;
33+
lastName: string;
34+
email: string;
35+
};
36+
37+
const generateTestData = (rows: number) => {
38+
return _.range(rows).map(i => ({
39+
id: i + 1,
40+
firstName: randFirstName(),
41+
// ...
42+
}));
43+
};
44+
45+
const columns: ColumnProps<TestData>[] = [
46+
{ key: "id", header: "ID", width: 50 },
47+
{ key: "firstName", header: "First", width: 120 },
48+
{ key: "lastName", header: "Last", width: 120 },
49+
{ key: "email", header: "Email", width: 250 }
50+
];
51+
52+
const NUM_ROWS = 50;
53+
54+
const Example = () => {
55+
const [multiplier, setMultiplier] = useState(1);
56+
const [data, setData] = useState(generateTestData(NUM_ROWS));
57+
58+
const fetchData = useCallback(async () => {
59+
await new Promise(r => window.setTimeout(r, 50000));
60+
61+
setMultiplier(prev => prev + 1);
62+
setData(generateTestData((multiplier + 1) * NUM_ROWS));
63+
return true;
64+
}, [multiplier]);
65+
66+
return <Table data={data} columns={columns} onLoadRows={() => fetchData()} />;
67+
};
68+
`;
69+
70+
const NUM_ROWS = 50;
71+
72+
const Example13 = () => {
73+
useTitle("Infinite Table");
74+
useSource(Source);
75+
const [multiplier, setMultiplier] = useState(1);
76+
const [data, setData] = useState(generateTestData(NUM_ROWS));
77+
78+
const fetchData = useCallback(async () => {
79+
await new Promise(r => window.setTimeout(r, 500));
80+
81+
setMultiplier(prev => prev + 1);
82+
setData(generateTestData((multiplier + 1) * NUM_ROWS));
83+
return true;
84+
}, [multiplier]);
85+
86+
return <Table data={data} columns={columns} onLoadRows={() => fetchData()} />;
87+
};
88+
89+
export { Example13 };

0 commit comments

Comments
 (0)