From f2ee94559632aa4e859157cdc357c0497287f096 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Mon, 30 Jun 2025 15:03:57 -0500 Subject: [PATCH 01/16] Take delay as input --- packages/ui-components/CHANGELOG.md | 4 ++++ packages/ui-components/src/infinite-scroll.ts | 8 ++++++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/CHANGELOG.md b/packages/ui-components/CHANGELOG.md index e6ccbf2c..23e8c232 100644 --- a/packages/ui-components/CHANGELOG.md +++ b/packages/ui-components/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [unreleased] - 2025-06-30 + +Updated infinite scorlling to take `delay` as input param + ## [4.3.0] - 2025-06-25 Updated infinite scrolling diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index 4fbf5042..b460dc2c 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -41,7 +41,8 @@ interface InfiniteScrollProps extends Omit, "params"> { resultsComponent?: React.ComponentType<{ data: T[] }>; perPage?: number; startPage?: number; - initialData?: T[]; // to allow for server-side rendering for initial state + initialData?: T[]; + delay?: number; } type UpdateState = { type: "update-state"; spec: Spec> }; @@ -88,11 +89,12 @@ export function InfiniteScroll(props) { loadMore, offset = 0, isLoading, + delay = 100, } = props; const { ref, inView } = useInView({ rootMargin: `0px 0px ${offset}px 0px`, trackVisibility: true, - delay: 100, + delay: delay >= 100 ? delay : 100, }); const shouldLoadMore = hasMore && inView; @@ -189,6 +191,7 @@ function InfiniteScrollView(props: InfiniteScrollProps) { perPage = 10, startPage = 0, initialItems = [], + delay, } = props; const { get } = useAPIActions(); const { getCount, getNextParams, getItems, hasMore } = props; @@ -297,6 +300,7 @@ function InfiniteScrollView(props: InfiniteScrollProps) { loader: placeholder, useWindow: true, className, + delay }, [ h.if(isEmpty)(emptyPlaceholder), From 83e26504680af3ac30931f454aa6ade17838e7d3 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Mon, 30 Jun 2025 20:05:21 +0000 Subject: [PATCH 02/16] Apply formatting changes --- packages/ui-components/src/infinite-scroll.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index b460dc2c..2865ce1e 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -94,7 +94,7 @@ export function InfiniteScroll(props) { const { ref, inView } = useInView({ rootMargin: `0px 0px ${offset}px 0px`, trackVisibility: true, - delay: delay >= 100 ? delay : 100, + delay: delay >= 100 ? delay : 100, }); const shouldLoadMore = hasMore && inView; @@ -300,7 +300,7 @@ function InfiniteScrollView(props: InfiniteScrollProps) { loader: placeholder, useWindow: true, className, - delay + delay, }, [ h.if(isEmpty)(emptyPlaceholder), From 315ea80aae5a9eb2504dfd254f62775c5555f8fa Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Mon, 30 Jun 2025 15:06:31 -0500 Subject: [PATCH 03/16] DEfault --- packages/ui-components/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-components/CHANGELOG.md b/packages/ui-components/CHANGELOG.md index 23e8c232..bc1acb75 100644 --- a/packages/ui-components/CHANGELOG.md +++ b/packages/ui-components/CHANGELOG.md @@ -2,7 +2,7 @@ ## [unreleased] - 2025-06-30 -Updated infinite scorlling to take `delay` as input param +Updated infinite scorlling to take `delay` as input param (default 100ms) ## [4.3.0] - 2025-06-25 From 9041fbe9fdb080e774467f34b5d7ee54b13a4eab Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Tue, 1 Jul 2025 10:58:33 -0500 Subject: [PATCH 04/16] Test --- .../stories/infinite-scroll.stories.ts | 132 +++--------------- 1 file changed, 18 insertions(+), 114 deletions(-) diff --git a/packages/ui-components/stories/infinite-scroll.stories.ts b/packages/ui-components/stories/infinite-scroll.stories.ts index 7b72c577..5165156d 100644 --- a/packages/ui-components/stories/infinite-scroll.stories.ts +++ b/packages/ui-components/stories/infinite-scroll.stories.ts @@ -16,126 +16,30 @@ const Template: ComponentStory = (args) => export const Primary = Template.bind({}); // More on args: https://storybook.js.org/docs/react/writing-stories/args +const initialItems = [ + { + concept_id: 1, + id: null, + name: "Aaron", + rank: null, + strat_names: "Aaron", + strat_ids: "60001", + all_names: "Aaron,Aaron", + combined_id: 1, + strat_ranks: "Fm", + } +]; + Primary.args = { params: { - combined_id: "gt.10", - limit: 10, + combined_id: "gt." + initialItems[0].combined_id, + limit: 1, order: "combined_id.asc", }, route: "https://dev.macrostrat.org/api/pg/strat_combined", getNextParams, - initialItems: [ - { - concept_id: 1, - id: null, - name: "Aaron", - rank: null, - strat_names: "Aaron", - strat_ids: "60001", - all_names: "Aaron,Aaron", - combined_id: 1, - strat_ranks: "Fm", - }, - { - concept_id: 2, - id: null, - name: "Abbott", - rank: null, - strat_names: null, - strat_ids: null, - all_names: "Abbott,", - combined_id: 2, - strat_ranks: null, - }, - { - concept_id: 3, - id: null, - name: "Abel Gap", - rank: null, - strat_names: "Abel Gap", - strat_ids: "7637", - all_names: "Abel Gap,Abel Gap", - combined_id: 3, - strat_ranks: "Fm", - }, - { - concept_id: 4, - id: null, - name: "Aberdeen", - rank: null, - strat_names: "Aberdeen,Aberdeen Sandstone", - strat_ids: "60004,60005", - all_names: "Aberdeen,Aberdeen,Aberdeen Sandstone", - combined_id: 4, - strat_ranks: "Mbr,Mbr", - }, - { - concept_id: 5, - id: null, - name: "Abingdon", - rank: null, - strat_names: "Abingdon Coal", - strat_ids: "60006", - all_names: "Abingdon,Abingdon Coal", - combined_id: 5, - strat_ranks: "Mbr", - }, - { - concept_id: 6, - id: null, - name: "Able", - rank: null, - strat_names: "Able", - strat_ids: "6899", - all_names: "Able,Able", - combined_id: 6, - strat_ranks: "Mbr", - }, - { - concept_id: 7, - id: null, - name: "Abrahams Creek", - rank: null, - strat_names: "Abrahams Creek", - strat_ids: "60008", - all_names: "Abrahams Creek,Abrahams Creek", - combined_id: 7, - strat_ranks: "Mbr", - }, - { - concept_id: 8, - id: null, - name: "Absalona", - rank: null, - strat_names: "Absalona,Absalona", - strat_ids: "60009,10498", - all_names: "Absalona,Absalona,Absalona", - combined_id: 8, - strat_ranks: "Fm,Fm", - }, - { - concept_id: 9, - id: null, - name: "Accomac Canyon", - rank: null, - strat_names: "Accomac Canyon Alloformation", - strat_ids: "60011", - all_names: "Accomac Canyon,Accomac Canyon Alloformation", - combined_id: 9, - strat_ranks: "Fm", - }, - { - concept_id: 10, - id: null, - name: "Accomack", - rank: null, - strat_names: "Accomack", - strat_ids: "60012", - all_names: "Accomack,Accomack", - combined_id: 10, - strat_ranks: "Mbr", - }, - ], + delay: 200, + initialItems }; function getNextParams(response, params) { From 315cef13b0c5462ea4d52269edee2480b22a8587 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Tue, 1 Jul 2025 11:13:35 -0500 Subject: [PATCH 05/16] Change wrong prop --- packages/ui-components/src/infinite-scroll.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index 2865ce1e..84f43e36 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -41,7 +41,7 @@ interface InfiniteScrollProps extends Omit, "params"> { resultsComponent?: React.ComponentType<{ data: T[] }>; perPage?: number; startPage?: number; - initialData?: T[]; + initialItems?: T[]; delay?: number; } From 036d8f556e43c272859b2d4c13ee803d42e1d625 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Tue, 1 Jul 2025 16:13:50 +0000 Subject: [PATCH 06/16] Apply formatting changes --- packages/ui-components/stories/infinite-scroll.stories.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/ui-components/stories/infinite-scroll.stories.ts b/packages/ui-components/stories/infinite-scroll.stories.ts index 5165156d..f0ae4151 100644 --- a/packages/ui-components/stories/infinite-scroll.stories.ts +++ b/packages/ui-components/stories/infinite-scroll.stories.ts @@ -27,7 +27,7 @@ const initialItems = [ all_names: "Aaron,Aaron", combined_id: 1, strat_ranks: "Fm", - } + }, ]; Primary.args = { @@ -39,7 +39,7 @@ Primary.args = { route: "https://dev.macrostrat.org/api/pg/strat_combined", getNextParams, delay: 200, - initialItems + initialItems, }; function getNextParams(response, params) { From 699674f5cc4808bbd5ec517c184f11a5dc92abac Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Tue, 1 Jul 2025 12:14:50 -0500 Subject: [PATCH 07/16] change delay --- packages/ui-components/stories/infinite-scroll.stories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui-components/stories/infinite-scroll.stories.ts b/packages/ui-components/stories/infinite-scroll.stories.ts index 5165156d..82843f69 100644 --- a/packages/ui-components/stories/infinite-scroll.stories.ts +++ b/packages/ui-components/stories/infinite-scroll.stories.ts @@ -38,7 +38,7 @@ Primary.args = { }, route: "https://dev.macrostrat.org/api/pg/strat_combined", getNextParams, - delay: 200, + delay: 100, initialItems }; From 79b3b2c8a48e4fa84ffefe4c2764a5a5d4f841d9 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 12:18:28 -0500 Subject: [PATCH 08/16] Strict mode enabled --- .storybook/preview.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 062d92f5..47d9a65f 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -8,6 +8,7 @@ import { DarkModeProvider } from "@macrostrat/ui-components"; import { useDarkMode } from "@vueless/storybook-dark-mode"; import { DocsContainer } from "./docs-container"; import { GeologicPatternProvider } from "@macrostrat/column-components"; +import { StrictMode } from "react"; FocusStyleManager.onlyShowFocusOnTabs(); @@ -53,9 +54,10 @@ export const parameters = { export const decorators = [ (renderStory) => { const isEnabled = useDarkMode(); - return h( - PatternProvider, - h(DarkModeProvider, { isEnabled }, renderStory()), + return h(StrictMode, + h(PatternProvider, + h(DarkModeProvider, { isEnabled }, renderStory()), + ) ); }, ]; From 4bbe2ab69bfedcbf36e79e7a20e5f0ba57bf98e9 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 13:31:08 -0500 Subject: [PATCH 09/16] First issue fixed --- packages/ui-components/src/infinite-scroll.ts | 4 ++-- packages/ui-components/stories/infinite-scroll.stories.ts | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index 84f43e36..5cbb86fb 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -263,6 +263,7 @@ function InfiniteScrollView(props: InfiniteScrollProps) { // Don't run on initial render if (isInitialRender.current) { isInitialRender.current = false; + dispatch({ type: "update-state", spec: { $set: initialState } }); return; } /* @@ -271,7 +272,6 @@ function InfiniteScrollView(props: InfiniteScrollProps) { // const success = await get(route, params, opts); // parseResponse(success, true) //if (state.items.length == 0 && state.isLoadingPage == null) return - dispatch({ type: "update-state", spec: { $set: initialState } }); //await loadNext(0) }, [isInitialRender, route, params, opts], @@ -344,4 +344,4 @@ InfiniteScrollView.defaultProps = { placeholder: (p: APIPlaceholderProps) => h(Spinner), }; -export { InfiniteScrollView }; +export { InfiniteScrollView }; \ No newline at end of file diff --git a/packages/ui-components/stories/infinite-scroll.stories.ts b/packages/ui-components/stories/infinite-scroll.stories.ts index f0ae4151..85f14453 100644 --- a/packages/ui-components/stories/infinite-scroll.stories.ts +++ b/packages/ui-components/stories/infinite-scroll.stories.ts @@ -43,7 +43,6 @@ Primary.args = { }; function getNextParams(response, params) { - console.log("getNextParams", response, params); return { ...params, combined_id: "gt." + response[response.length - 1].combined_id, From 76f1469312f4e45379c76fc04d1d3d7f119ba130 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 18:31:33 +0000 Subject: [PATCH 10/16] Apply formatting changes --- .storybook/preview.ts | 7 +++---- packages/ui-components/src/infinite-scroll.ts | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 47d9a65f..e6ca10e3 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -54,10 +54,9 @@ export const parameters = { export const decorators = [ (renderStory) => { const isEnabled = useDarkMode(); - return h(StrictMode, - h(PatternProvider, - h(DarkModeProvider, { isEnabled }, renderStory()), - ) + return h( + StrictMode, + h(PatternProvider, h(DarkModeProvider, { isEnabled }, renderStory())), ); }, ]; diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index 5cbb86fb..b409e346 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -344,4 +344,4 @@ InfiniteScrollView.defaultProps = { placeholder: (p: APIPlaceholderProps) => h(Spinner), }; -export { InfiniteScrollView }; \ No newline at end of file +export { InfiniteScrollView }; From e415482334e3a4a700f36387ac06aa64bb7de915 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 13:52:12 -0500 Subject: [PATCH 11/16] Remove strict mode --- .storybook/preview.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 47d9a65f..4c83bdd4 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -54,11 +54,9 @@ export const parameters = { export const decorators = [ (renderStory) => { const isEnabled = useDarkMode(); - return h(StrictMode, - h(PatternProvider, - h(DarkModeProvider, { isEnabled }, renderStory()), - ) - ); + return h(PatternProvider, + h(DarkModeProvider, { isEnabled }, renderStory()), + ) }, ]; From 355bc5cffeaf22633b63ffc8c365c28255ffefbe Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 18:53:08 +0000 Subject: [PATCH 12/16] Apply formatting changes --- .storybook/preview.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 4c83bdd4..8f8bc795 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -54,9 +54,10 @@ export const parameters = { export const decorators = [ (renderStory) => { const isEnabled = useDarkMode(); - return h(PatternProvider, + return h( + PatternProvider, h(DarkModeProvider, { isEnabled }, renderStory()), - ) + ); }, ]; From 0ed2ba947da69d47bceb93f438e4031b0e032256 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 13:53:17 -0500 Subject: [PATCH 13/16] Make same --- .storybook/preview.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 4c83bdd4..1e54a762 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -8,7 +8,6 @@ import { DarkModeProvider } from "@macrostrat/ui-components"; import { useDarkMode } from "@vueless/storybook-dark-mode"; import { DocsContainer } from "./docs-container"; import { GeologicPatternProvider } from "@macrostrat/column-components"; -import { StrictMode } from "react"; FocusStyleManager.onlyShowFocusOnTabs(); @@ -56,7 +55,7 @@ export const decorators = [ const isEnabled = useDarkMode(); return h(PatternProvider, h(DarkModeProvider, { isEnabled }, renderStory()), - ) + ); }, ]; From 5d794645dd3c86c17e6696f6cd06e0dc96fa7f7c Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 16:17:09 -0500 Subject: [PATCH 14/16] WORKS!! --- packages/ui-components/src/infinite-scroll.ts | 202 ++++++------------ 1 file changed, 71 insertions(+), 131 deletions(-) diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index b409e346..80bb99c4 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -1,7 +1,7 @@ // @ts-nocheck import h from "@macrostrat/hyper"; import update, { Spec } from "immutability-helper"; -import React, { useReducer, useEffect, useRef, useCallback, memo } from "react"; +import React, { useReducer, useEffect, useRef, useCallback } from "react"; import { Spinner, NonIdealState } from "@blueprintjs/core"; import { APIParams, QueryParams } from "./util/query-string"; import { useInView } from "react-intersection-observer"; @@ -20,36 +20,7 @@ interface ScrollState { pageIndex: number; } -type ScrollResponseItems = Pick< - ScrollState, - "count" | "hasMore" | "items" ->; - -interface InfiniteScrollProps extends Omit, "params"> { - getCount(r: T): number; - getNextParams(r: T, params: QueryParams): QueryParams; - getItems(r: T): any; - hasMore(res: T): boolean; - totalCount?: number; - // Only allow more restrictive parameter types - params: APIParams; - className?: string; - itemComponent?: React.ComponentType<{ data: T; index: number }>; - loadingPlaceholder?: React.ComponentType; - emptyPlaceholder?: React.ComponentType; - finishedPlaceholder?: React.ComponentType; - resultsComponent?: React.ComponentType<{ data: T[] }>; - perPage?: number; - startPage?: number; - initialItems?: T[]; - delay?: number; -} - type UpdateState = { type: "update-state"; spec: Spec> }; -type LoadNextPage = { - type: "load-next-page"; - page: number; -}; type LoadPage = { type: "load-page"; params: APIParams; @@ -57,66 +28,47 @@ type LoadPage = { callback(action: LoadPage): void; }; -type ScrollAction = UpdateState | LoadNextPage | LoadPage; - -type Reducer = ( - state: ScrollState, - action: ScrollAction, -) => ScrollState; +type ScrollAction = UpdateState | LoadPage; +type Reducer = (state: ScrollState, action: ScrollAction) => ScrollState; type Dispatch = (action: ScrollAction) => void; -function infiniteScrollReducer( - state: ScrollState, - action: ScrollAction, -) { +function infiniteScrollReducer(state: ScrollState, action: ScrollAction) { switch (action.type) { case "update-state": return update(state, action.spec); case "load-page": action.callback(action); return update(state, { - // @ts-ignore isLoadingPage: { $set: action.params.page ?? 0 }, }); } } export function InfiniteScroll(props) { - const { - hasMore, - children, - className, - loadMore, - offset = 0, - isLoading, - delay = 100, - } = props; + const { hasMore, children, className, loadMore, offset = 0, isLoading, delay = 100 } = props; const { ref, inView } = useInView({ rootMargin: `0px 0px ${offset}px 0px`, trackVisibility: true, delay: delay >= 100 ? delay : 100, }); - const shouldLoadMore = hasMore && inView; + // Only load more if not currently loading + const shouldLoadMore = hasMore && inView && !isLoading; useEffect(() => { - if (shouldLoadMore) loadMore(); - }, [shouldLoadMore, isLoading]); + if (shouldLoadMore) { + loadMore(); + } + }, [shouldLoadMore, loadMore]); return h("div.infinite-scroll-container", { className }, [ children, - //h.if(state.isLoadingPage != null)(placeholder), h("div.bottom-marker", { ref, style: { padding: "1px" } }), ]); } const Placeholder = (props) => { - const { - loading, - title = "No results yet", - description = null, - ...rest - } = props; + const { loading, title = "No results yet", description = null, ...rest } = props; return h("div.placeholder", [ h(NonIdealState, { @@ -172,11 +124,6 @@ function FinishedPlaceholder({ totalCount, ...rest }: { totalCount?: number }) { } function InfiniteScrollView(props: InfiniteScrollProps) { - /* - A container for cursor-based pagination. This is built for - the GeoDeepDive API right now, but it can likely be generalized - for other uses. - */ const { route, params, @@ -206,88 +153,84 @@ function InfiniteScrollView(props: InfiniteScrollProps) { pageIndex: startPage, }; - const pageOffset = 0; + const [state, dispatch] = useReducer>(infiniteScrollReducer, initialState); - const [state, dispatch] = useReducer>( - infiniteScrollReducer, - initialState, - ); + const loadingRef = useRef(false); + + const mountedRef = useRef(true); + useEffect(() => { + return () => { + mountedRef.current = false; + }; + }, []); const loadPage = useCallback( async (action: LoadPage) => { - const res = await get(route, action.params, opts); - const itemVals = getItems(res); - const ival = { $push: itemVals }; - const nextLength = state.items.length + itemVals.length; - const count = getCount(res); - // if (state.isLoadingPage == null) { - // // We have externally cancelled this request (by e.g. moving to a new results set) - // console.log("Loading cancelled") - // return - // } - - let p1: QueryParams = getNextParams(res, params); - let hasNextParams = p1 != null; - - action.dispatch({ - type: "update-state", - spec: { - items: ival, - // @ts-ignore - scrollParams: { $set: p1 }, - pageIndex: { $set: state.pageIndex + 1 }, - count: { $set: count }, - hasMore: { - $set: hasMore(res) && itemVals.length > 0 && hasNextParams, + if (loadingRef.current) return; // Prevent concurrent loads + loadingRef.current = true; + + dispatch(action); + + try { + const res = await get(route, action.params, opts); + if (!mountedRef.current) return; + + const itemVals = getItems(res); + const nextParams = getNextParams(res, action.params); + const count = getCount(res); + const more = hasMore(res) && itemVals.length > 0 && nextParams != null; + + action.dispatch({ + type: "update-state", + spec: { + items: { $push: itemVals }, + scrollParams: { $set: nextParams }, + pageIndex: { $set: state.pageIndex + 1 }, + count: { $set: count }, + hasMore: { $set: more }, + isLoadingPage: { $set: null }, + error: { $set: null }, }, - isLoadingPage: { $set: null }, - }, - }); + }); + } catch (error) { + if (!mountedRef.current) return; + action.dispatch({ + type: "update-state", + spec: { error: { $set: error }, isLoadingPage: { $set: null } }, + }); + } finally { + loadingRef.current = false; + } }, - [state.items, route, params, opts], + [get, route, opts, getItems, getNextParams, getCount, hasMore, state.pageIndex], ); const loadMore = useCallback(() => { + if (state.isLoadingPage !== null || !state.hasMore) return; dispatch({ type: "load-page", params: state.scrollParams, dispatch, - // @ts-ignore callback: loadPage, }); - }, [state.scrollParams, loadPage, route, params, opts]); + }, [state.isLoadingPage, state.hasMore, state.scrollParams, loadPage]); const isInitialRender = useRef(true); - const loadInitialData = useCallback( - function () { - // Don't run on initial render - if (isInitialRender.current) { - isInitialRender.current = false; - dispatch({ type: "update-state", spec: { $set: initialState } }); - return; - } - /* - Get the initial dataset - */ - // const success = await get(route, params, opts); - // parseResponse(success, true) - //if (state.items.length == 0 && state.isLoadingPage == null) return - //await loadNext(0) - }, - [isInitialRender, route, params, opts], - ); - useEffect(loadInitialData, [props.route, props.params]); + useEffect(() => { + if (isInitialRender.current) { + isInitialRender.current = false; + if (state.items.length === 0) { + loadMore(); + } + } + }, [loadMore, state.items.length]); if (state == null) return null; - //useAsyncEffect(getInitialData, [route, params]); - - //const showLoader = state.isLoadingPage != null && state.items.length > 0 - const data = state.items; const isLoading = state.isLoadingPage != null; - const isEmpty = data.length == 0 && !isLoading; + const isEmpty = data.length === 0 && !isLoading; const isFinished = !state.hasMore && !isLoading; const totalCount = props.totalCount ?? state.count; @@ -301,6 +244,7 @@ function InfiniteScrollView(props: InfiniteScrollProps) { useWindow: true, className, delay, + isLoading, }, [ h.if(isEmpty)(emptyPlaceholder), @@ -308,11 +252,8 @@ function InfiniteScrollView(props: InfiniteScrollProps) { h( resultsComponent, { data }, - data.map((d, i) => { - return h(itemComponent, { key: i, data: d, index: i }); - }), + data.map((d, i) => h(itemComponent, { key: i, data: d, index: i })), ), - // @ts-ignore h.if(isLoading)(loadingPlaceholder, { totalCount, scrollParams: state.scrollParams, @@ -320,7 +261,6 @@ function InfiniteScrollView(props: InfiniteScrollProps) { loadedCount: data.length, perPage, }), - // @ts-ignore h.if(isFinished)(finishedPlaceholder, { totalCount }), ]), ], @@ -328,13 +268,13 @@ function InfiniteScrollView(props: InfiniteScrollProps) { } InfiniteScrollView.defaultProps = { - hasMore(res) { + hasMore() { return true; }, getItems(d) { return d; }, - getCount(d) { + getCount() { return null; }, getNextParams(response, params) { From 877ee13f61fc89129bc56ca51b9addef84f46092 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 21:17:36 +0000 Subject: [PATCH 15/16] Apply formatting changes --- packages/ui-components/src/infinite-scroll.ts | 49 +++++++++++++++---- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/packages/ui-components/src/infinite-scroll.ts b/packages/ui-components/src/infinite-scroll.ts index 80bb99c4..89c94aa5 100644 --- a/packages/ui-components/src/infinite-scroll.ts +++ b/packages/ui-components/src/infinite-scroll.ts @@ -29,10 +29,16 @@ type LoadPage = { }; type ScrollAction = UpdateState | LoadPage; -type Reducer = (state: ScrollState, action: ScrollAction) => ScrollState; +type Reducer = ( + state: ScrollState, + action: ScrollAction, +) => ScrollState; type Dispatch = (action: ScrollAction) => void; -function infiniteScrollReducer(state: ScrollState, action: ScrollAction) { +function infiniteScrollReducer( + state: ScrollState, + action: ScrollAction, +) { switch (action.type) { case "update-state": return update(state, action.spec); @@ -45,7 +51,15 @@ function infiniteScrollReducer(state: ScrollState, action: ScrollAction } export function InfiniteScroll(props) { - const { hasMore, children, className, loadMore, offset = 0, isLoading, delay = 100 } = props; + const { + hasMore, + children, + className, + loadMore, + offset = 0, + isLoading, + delay = 100, + } = props; const { ref, inView } = useInView({ rootMargin: `0px 0px ${offset}px 0px`, trackVisibility: true, @@ -68,7 +82,12 @@ export function InfiniteScroll(props) { } const Placeholder = (props) => { - const { loading, title = "No results yet", description = null, ...rest } = props; + const { + loading, + title = "No results yet", + description = null, + ...rest + } = props; return h("div.placeholder", [ h(NonIdealState, { @@ -153,7 +172,10 @@ function InfiniteScrollView(props: InfiniteScrollProps) { pageIndex: startPage, }; - const [state, dispatch] = useReducer>(infiniteScrollReducer, initialState); + const [state, dispatch] = useReducer>( + infiniteScrollReducer, + initialState, + ); const loadingRef = useRef(false); @@ -169,11 +191,11 @@ function InfiniteScrollView(props: InfiniteScrollProps) { if (loadingRef.current) return; // Prevent concurrent loads loadingRef.current = true; - dispatch(action); + dispatch(action); try { const res = await get(route, action.params, opts); - if (!mountedRef.current) return; + if (!mountedRef.current) return; const itemVals = getItems(res); const nextParams = getNextParams(res, action.params); @@ -202,7 +224,16 @@ function InfiniteScrollView(props: InfiniteScrollProps) { loadingRef.current = false; } }, - [get, route, opts, getItems, getNextParams, getCount, hasMore, state.pageIndex], + [ + get, + route, + opts, + getItems, + getNextParams, + getCount, + hasMore, + state.pageIndex, + ], ); const loadMore = useCallback(() => { @@ -221,7 +252,7 @@ function InfiniteScrollView(props: InfiniteScrollProps) { if (isInitialRender.current) { isInitialRender.current = false; if (state.items.length === 0) { - loadMore(); + loadMore(); } } }, [loadMore, state.items.length]); From 08cf903b3272ea4fb3e999a2e639e65f02580229 Mon Sep 17 00:00:00 2001 From: davidsklar99 Date: Wed, 2 Jul 2025 16:18:59 -0500 Subject: [PATCH 16/16] Update package --- packages/ui-components/CHANGELOG.md | 5 +++-- packages/ui-components/package.json | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/ui-components/CHANGELOG.md b/packages/ui-components/CHANGELOG.md index bc1acb75..49645ceb 100644 --- a/packages/ui-components/CHANGELOG.md +++ b/packages/ui-components/CHANGELOG.md @@ -1,8 +1,9 @@ # Changelog -## [unreleased] - 2025-06-30 +## [4.3.1] - 2025-07-02 -Updated infinite scorlling to take `delay` as input param (default 100ms) +- Updated infinite scorlling to take `delay` as input param (default 100ms) +- Fixed duplicate data issue in Strict Mode ## [4.3.0] - 2025-06-25 diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index a3777cbc..a141157a 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -1,6 +1,6 @@ { "name": "@macrostrat/ui-components", - "version": "4.3.0", + "version": "4.3.1", "description": "UI components for React and Blueprint.js", "main": "dist/cjs/index.js", "module": "dist/esm/index.js",