Skip to content

Commit 982cf4a

Browse files
authored
Merge pull request #91 from UW-Macrostrat/load-more
Infinite scroll container
2 parents e1ef563 + 11aab6b commit 982cf4a

File tree

3 files changed

+155
-6
lines changed

3 files changed

+155
-6
lines changed

packages/ui-components/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
Added the following components:
55
- SearchBar - general input field search bar
66
- CreateCheckins - takes checkin data as input and creates checkins
7+
Updated infinite scrolling
8+
- LoadMoreTrigger that changes startingID, allowing for keyset pagination on next page
9+
- Added InfiniteScrollContainer that allows for an infinite scrolling list using the LoadMoreTrigger, can be filterable
710

811
## [4.2.0] - 2025-04-09
912

packages/ui-components/src/infinite-scroll.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ interface InfiniteScrollProps<T> extends Omit<APIResultProps<T>, "params"> {
4141
resultsComponent?: React.ComponentType<{ data: T[] }>;
4242
perPage?: number;
4343
startPage?: number;
44+
initialData?: T[]; // to allow for server-side rendering for initial state
4445
}
4546

4647
type UpdateState<T> = { type: "update-state"; spec: Spec<ScrollState<T>> };
@@ -91,6 +92,7 @@ export function InfiniteScroll(props) {
9192
const { ref, inView } = useInView({
9293
rootMargin: `0px 0px ${offset}px 0px`,
9394
trackVisibility: true,
95+
delay: 100,
9496
});
9597

9698
const shouldLoadMore = hasMore && inView;
@@ -102,7 +104,7 @@ export function InfiniteScroll(props) {
102104
return h("div.infinite-scroll-container", { className }, [
103105
children,
104106
//h.if(state.isLoadingPage != null)(placeholder),
105-
h("div.bottom-marker", { ref }),
107+
h("div.bottom-marker", { ref, style: { padding: "1px"} }),
106108
]);
107109
}
108110

@@ -186,12 +188,13 @@ function InfiniteScrollView<T>(props: InfiniteScrollProps<T>) {
186188
resultsComponent = "div.results",
187189
perPage = 10,
188190
startPage = 0,
191+
initialItems = [],
189192
} = props;
190193
const { get } = useAPIActions();
191194
const { getCount, getNextParams, getItems, hasMore } = props;
192195

193196
const initialState: ScrollState<T> = {
194-
items: [],
197+
items: initialItems,
195198
scrollParams: params,
196199
count: null,
197200
error: null,
@@ -210,12 +213,10 @@ function InfiniteScrollView<T>(props: InfiniteScrollProps<T>) {
210213
const loadPage = useCallback(
211214
async (action: LoadPage<T>) => {
212215
const res = await get(route, action.params, opts);
213-
console.log("Loaded page with params", action.params);
214216
const itemVals = getItems(res);
215217
const ival = { $push: itemVals };
216218
const nextLength = state.items.length + itemVals.length;
217219
const count = getCount(res);
218-
console.log(state.items);
219220
// if (state.isLoadingPage == null) {
220221
// // We have externally cancelled this request (by e.g. moving to a new results set)
221222
// console.log("Loading cancelled")
@@ -224,7 +225,6 @@ function InfiniteScrollView<T>(props: InfiniteScrollProps<T>) {
224225

225226
let p1: QueryParams = getNextParams(res, params);
226227
let hasNextParams = p1 != null;
227-
console.log("Next page parameters", p1);
228228

229229
action.dispatch({
230230
type: "update-state",
@@ -262,7 +262,6 @@ function InfiniteScrollView<T>(props: InfiniteScrollProps<T>) {
262262
isInitialRender.current = false;
263263
return;
264264
}
265-
console.log("Resetting to initial data");
266265
/*
267266
Get the initial dataset
268267
*/
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
import { ComponentStory, ComponentMeta } from "@storybook/react";
2+
import h from "@macrostrat/hyper";
3+
import { InfiniteScrollView } from "../src/infinite-scroll";
4+
5+
// More on default export: https://storybook.js.org/docs/react/writing-stories/introduction#default-export
6+
export default {
7+
title: "UI components/Infinite scroll",
8+
component: InfiniteScrollView,
9+
// More on argTypes: https://storybook.js.org/docs/react/api/argtypes
10+
argTypes: {},
11+
} as ComponentMeta<typeof InfiniteScrollView>;
12+
13+
// More on component templates: https://storybook.js.org/docs/react/writing-stories/introduction#using-args
14+
const Template: ComponentStory<typeof InfiniteScrollView> = (args) =>
15+
h(InfiniteScrollView, args);
16+
17+
export const Primary = Template.bind({});
18+
// More on args: https://storybook.js.org/docs/react/writing-stories/args
19+
Primary.args = {
20+
params: {
21+
combined_id: "gt.10",
22+
limit: 10,
23+
order: "combined_id.asc",
24+
},
25+
route: "https://dev.macrostrat.org/api/pg/strat_combined",
26+
getNextParams,
27+
initialItems: [
28+
{
29+
"concept_id": 1,
30+
"id": null,
31+
"name": "Aaron",
32+
"rank": null,
33+
"strat_names": "Aaron",
34+
"strat_ids": "60001",
35+
"all_names": "Aaron,Aaron",
36+
"combined_id": 1,
37+
"strat_ranks": "Fm"
38+
},
39+
{
40+
"concept_id": 2,
41+
"id": null,
42+
"name": "Abbott",
43+
"rank": null,
44+
"strat_names": null,
45+
"strat_ids": null,
46+
"all_names": "Abbott,",
47+
"combined_id": 2,
48+
"strat_ranks": null
49+
},
50+
{
51+
"concept_id": 3,
52+
"id": null,
53+
"name": "Abel Gap",
54+
"rank": null,
55+
"strat_names": "Abel Gap",
56+
"strat_ids": "7637",
57+
"all_names": "Abel Gap,Abel Gap",
58+
"combined_id": 3,
59+
"strat_ranks": "Fm"
60+
},
61+
{
62+
"concept_id": 4,
63+
"id": null,
64+
"name": "Aberdeen",
65+
"rank": null,
66+
"strat_names": "Aberdeen,Aberdeen Sandstone",
67+
"strat_ids": "60004,60005",
68+
"all_names": "Aberdeen,Aberdeen,Aberdeen Sandstone",
69+
"combined_id": 4,
70+
"strat_ranks": "Mbr,Mbr"
71+
},
72+
{
73+
"concept_id": 5,
74+
"id": null,
75+
"name": "Abingdon",
76+
"rank": null,
77+
"strat_names": "Abingdon Coal",
78+
"strat_ids": "60006",
79+
"all_names": "Abingdon,Abingdon Coal",
80+
"combined_id": 5,
81+
"strat_ranks": "Mbr"
82+
},
83+
{
84+
"concept_id": 6,
85+
"id": null,
86+
"name": "Able",
87+
"rank": null,
88+
"strat_names": "Able",
89+
"strat_ids": "6899",
90+
"all_names": "Able,Able",
91+
"combined_id": 6,
92+
"strat_ranks": "Mbr"
93+
},
94+
{
95+
"concept_id": 7,
96+
"id": null,
97+
"name": "Abrahams Creek",
98+
"rank": null,
99+
"strat_names": "Abrahams Creek",
100+
"strat_ids": "60008",
101+
"all_names": "Abrahams Creek,Abrahams Creek",
102+
"combined_id": 7,
103+
"strat_ranks": "Mbr"
104+
},
105+
{
106+
"concept_id": 8,
107+
"id": null,
108+
"name": "Absalona",
109+
"rank": null,
110+
"strat_names": "Absalona,Absalona",
111+
"strat_ids": "60009,10498",
112+
"all_names": "Absalona,Absalona,Absalona",
113+
"combined_id": 8,
114+
"strat_ranks": "Fm,Fm"
115+
},
116+
{
117+
"concept_id": 9,
118+
"id": null,
119+
"name": "Accomac Canyon",
120+
"rank": null,
121+
"strat_names": "Accomac Canyon Alloformation",
122+
"strat_ids": "60011",
123+
"all_names": "Accomac Canyon,Accomac Canyon Alloformation",
124+
"combined_id": 9,
125+
"strat_ranks": "Fm"
126+
},
127+
{
128+
"concept_id": 10,
129+
"id": null,
130+
"name": "Accomack",
131+
"rank": null,
132+
"strat_names": "Accomack",
133+
"strat_ids": "60012",
134+
"all_names": "Accomack,Accomack",
135+
"combined_id": 10,
136+
"strat_ranks": "Mbr"
137+
}
138+
],
139+
};
140+
141+
function getNextParams(response, params) {
142+
console.log("getNextParams", response, params);
143+
return {
144+
...params,
145+
combined_id: "gt." + response[response.length - 1].combined_id,
146+
};
147+
}

0 commit comments

Comments
 (0)