S
SolidJS2y ago
Dog

createInfiniteScroll from @solid-primitives/pagination with createServerData$ and useRouteData

I'm trying to figure out how to get createInfiniteScroll from @solid-primitives/pagination to work with createServerData$ and useRouteData  I believe createInfiniteScroll expects a fetcher function that returns a Promise<T[]> per the definition https://github.com/solidjs-community/solid-primitives/tree/main/packages/pagination#definition  But I think createServerData$ is like createResource in that it takes an asynchronous fetcher function and returns a signal that is updated with the resulting data when the fetcher completes.  I'm currently trying something like this for createServerData$:
const fetcher = (page: number) => {
return createServerData$(async () => {
const elements: string[] = [];
const res = await fetch(`https://openlibrary.org/search.json?q=hello%20world&page=${page + 1}`, {
method: "GET",
});
if (res.ok) {
const json = await res.json();
json.docs.forEach((b: any) => elements.push(b.title));
}
return elements;
});
}

const [pages, infiniteScrollLoader, { end }] = createInfiniteScroll(fetcher);
const fetcher = (page: number) => {
return createServerData$(async () => {
const elements: string[] = [];
const res = await fetch(`https://openlibrary.org/search.json?q=hello%20world&page=${page + 1}`, {
method: "GET",
});
if (res.ok) {
const json = await res.json();
json.docs.forEach((b: any) => elements.push(b.title));
}
return elements;
});
}

const [pages, infiniteScrollLoader, { end }] = createInfiniteScroll(fetcher);
 when returning pages() from that I'm getting the errors: unsupported type content is not iterable  And trying this for createServerData$ and useRouteData:
const routeData = (page: number) => {
return createServerData$(async () => {
const elements: string[] = [];
const res = await fetch(`https://openlibrary.org/search.json?q=hello%20world&page=${page + 1}`, {
method: "GET",
});
if (res.ok) {
const json = await res.json();
json.docs.forEach((b: any) => elements.push(b.title));
}
return elements;
});
}

const data = useRouteData<typeof routeData>();
const [pages, infiniteScrollLoader, { end }] = createInfiniteScroll(data);
const routeData = (page: number) => {
return createServerData$(async () => {
const elements: string[] = [];
const res = await fetch(`https://openlibrary.org/search.json?q=hello%20world&page=${page + 1}`, {
method: "GET",
});
if (res.ok) {
const json = await res.json();
json.docs.forEach((b: any) => elements.push(b.title));
}
return elements;
});
}

const data = useRouteData<typeof routeData>();
const [pages, infiniteScrollLoader, { end }] = createInfiniteScroll(data);
 when returning pages() from that I'm getting the error: An error occured while server rendering /pagination: renderToString timed out
5 Replies
Alex Lohr
Alex Lohr2y ago
I think this primitive is not ready for solid-start yet. That's a use case we still need to investigate.
Peixe
Peixe2y ago
I'm attempting to use it with server$ Seems to be "working" Except the blinking in the page For some reason when calling the server$ it's removing all the html in body basically Adding the server$ call inside a setTimeout and calling setPages seems to be an ugly workaround I ended up making my own infinite scroll
MegaCookie
MegaCookie14mo ago
Hi! I am now also looking into infinite scroll with server fetching, and could use some inspiration 😄, could you share your solution?
Peixe
Peixe14mo ago
Hi @MegaCookie Sorry for making you wait
import {Refresh} from "~/components/ui/Icon/refresh";
import {Accessor, Component, createMemo, createSignal, JSX, onMount, Show} from "solid-js";
import {isVisible} from "~/lib/utils/element/isVisible";

export type InfiniteScrollProps = {
data: Accessor<any[] | null | undefined>
totalCount: Accessor<number | null | undefined>
loadingComponent?: Component
load: () => void
loading: Accessor<boolean>
}

export function createInfiniteScroll(props: InfiniteScrollProps): [isLoading: Accessor<boolean>, loading: JSX.Element] {
let loadingRef = null as HTMLElement | null
let loading = props.loadingComponent
? <props.loadingComponent ref={loadingRef!}/>
: <div class='flex flex-row justify-center space-x-2 w-full'>
<Refresh ref={loadingRef! as unknown as SVGSVGElement} class='animate-spin'/>
<span>Loading</span>
</div>

const loadIfNotLoading = () => {
if (props.loading()) return
if (reachedEnd()) return
props.load()
}

const reachedEnd = createMemo(() => props.data()?.length === props.totalCount())

onMount(() => {
if (loadingRef && isVisible(loadingRef)) loadIfNotLoading()
window.addEventListener('scroll', () => {
if (loadingRef && isVisible(loadingRef)) {
loadIfNotLoading()
}
})
})

return (
<Show when={!reachedEnd()}>
{loading}
</Show>
)
}
import {Refresh} from "~/components/ui/Icon/refresh";
import {Accessor, Component, createMemo, createSignal, JSX, onMount, Show} from "solid-js";
import {isVisible} from "~/lib/utils/element/isVisible";

export type InfiniteScrollProps = {
data: Accessor<any[] | null | undefined>
totalCount: Accessor<number | null | undefined>
loadingComponent?: Component
load: () => void
loading: Accessor<boolean>
}

export function createInfiniteScroll(props: InfiniteScrollProps): [isLoading: Accessor<boolean>, loading: JSX.Element] {
let loadingRef = null as HTMLElement | null
let loading = props.loadingComponent
? <props.loadingComponent ref={loadingRef!}/>
: <div class='flex flex-row justify-center space-x-2 w-full'>
<Refresh ref={loadingRef! as unknown as SVGSVGElement} class='animate-spin'/>
<span>Loading</span>
</div>

const loadIfNotLoading = () => {
if (props.loading()) return
if (reachedEnd()) return
props.load()
}

const reachedEnd = createMemo(() => props.data()?.length === props.totalCount())

onMount(() => {
if (loadingRef && isVisible(loadingRef)) loadIfNotLoading()
window.addEventListener('scroll', () => {
if (loadingRef && isVisible(loadingRef)) {
loadIfNotLoading()
}
})
})

return (
<Show when={!reachedEnd()}>
{loading}
</Show>
)
}
With that I've used it like this:
const loadingElement = createInfiniteScroll({
data,
totalCount,
loading,
load: fetch,
})
const loadingElement = createInfiniteScroll({
data,
totalCount,
loading,
load: fetch,
})
data is the current data, totalCount is the total amount of data with the current filter (if there's any filter) in the database Loading is if it's currently loading data Load is the method that's called when the user reaches the end and it still has data to load as done by !reachedEnd() My fetcher is just a server$ function as I use solid-start
export function isVisible(elm: HTMLElement) {
const rect = elm.getBoundingClientRect();
const viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return document.body.contains(elm) && !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
export function isVisible(elm: HTMLElement) {
const rect = elm.getBoundingClientRect();
const viewHeight = Math.max(document.documentElement.clientHeight, window.innerHeight);
return document.body.contains(elm) && !(rect.bottom < 0 || rect.top - viewHeight >= 0);
}
Some improvements that can be made: adding onCleanup make the loading element customizable maybe storing getBoundingClientRect on mount instead of on scroll Hope this helps you!
MegaCookie
MegaCookie14mo ago
Thanks a lot for your detailed answer 🙏
Want results from more Discord servers?
Add your server