S
SolidJS3mo ago
Reve

Implement 'load more' feature efficiently

I read the docs and searched this for like 3 hours but still can't find a good solution. Here is what my code currently looks like:
import { For, Show, createResource } from 'solid-js';
import Post from './Post';

function getPosts(startID: number) {
console.log('Loading posts starting from ID:', startID);

return fetch(`/api/post/get?startID=${startID}&limit=10`)
// @ts-ignore
.then((res): Promise<Post[] | undefined> => {
if (res.ok) return res.json();
})
.catch(() => { });
}

// TODO: Load more posts somehow
export default function Posts() {
const [startID, setStartID] = createSignal(-1);
const [loadedPosts] = createResource(startID, getPosts);
const currentPosts = loadedPosts();

return (
<div class='grid grid-cols-1 gap-12 mt-12'>
<Show when={typeof currentPosts !== 'undefined' && currentPosts.length !== 0}>
<For each={currentPosts!}>{Post}</For>
</Show>
</div>
);
}
import { For, Show, createResource } from 'solid-js';
import Post from './Post';

function getPosts(startID: number) {
console.log('Loading posts starting from ID:', startID);

return fetch(`/api/post/get?startID=${startID}&limit=10`)
// @ts-ignore
.then((res): Promise<Post[] | undefined> => {
if (res.ok) return res.json();
})
.catch(() => { });
}

// TODO: Load more posts somehow
export default function Posts() {
const [startID, setStartID] = createSignal(-1);
const [loadedPosts] = createResource(startID, getPosts);
const currentPosts = loadedPosts();

return (
<div class='grid grid-cols-1 gap-12 mt-12'>
<Show when={typeof currentPosts !== 'undefined' && currentPosts.length !== 0}>
<For each={currentPosts!}>{Post}</For>
</Show>
</div>
);
}
You don't need to care about the Post type and component they are just there to display a post My idea was to use an array to save the loaded posts and when it needs to load more I changed the id to be the last loaded post id
11 Replies
Maciek50322
Maciek503223mo ago
when you do
const currentPosts = loadedPosts();
const currentPosts = loadedPosts();
outside reactive scope (function / jsx / effect), it's assigned only once and doesn't wait for response I'd try something like that
export default function Posts() {
const [startID, setStartID] = createSignal(-1);
const [loadedPosts] = createResource(startID, getPosts);

const allPosts = createMemo((prev) => {
const posts = loadedPosts();
const start = untrack(startId);
if (start < 0 || !posts) return prev;
for (let i = 0; i < posts.length; i++) {
prev[start + i] = posts[i];
}
return [...prev];
}, [] /* initial value */)

return (
<div class='grid grid-cols-1 gap-12 mt-12'>
<Show when={typeof currentPosts !== 'undefined' && currentPosts.length !== 0}>
<For each={allPosts}>{Post}</For>
</Show>
</div>
);
}
export default function Posts() {
const [startID, setStartID] = createSignal(-1);
const [loadedPosts] = createResource(startID, getPosts);

const allPosts = createMemo((prev) => {
const posts = loadedPosts();
const start = untrack(startId);
if (start < 0 || !posts) return prev;
for (let i = 0; i < posts.length; i++) {
prev[start + i] = posts[i];
}
return [...prev];
}, [] /* initial value */)

return (
<div class='grid grid-cols-1 gap-12 mt-12'>
<Show when={typeof currentPosts !== 'undefined' && currentPosts.length !== 0}>
<For each={allPosts}>{Post}</For>
</Show>
</div>
);
}
Didn't exactly check if it works, but it's something you can base on. The prev in memo holds previous value that it returned, or inital value before it returned anything. The memo is only depended on loadedPosts() so it's called directly, it shouldn't trigger when startId changes, so it's untracked. Then fill previous array with new values and return copy of array, to trigger other updates. If we returned only posts instead of [...posts], then wherever allPosts() is used, wouldn't be updated, because it would be same object. We don't need to update it though when there are no new posts loaded yet, thet's why after if (start < 0 || !posts) we have just return prev;
Maciek50322
Maciek503223mo ago
Or you can try using something ready like https://primitives.solidjs.community/package/pagination
Solid Primitives
A library of high-quality primitives that extend SolidJS reactivity
Reve
Reve3mo ago
The point is that I don't waste memory to create a new list though Btw here's my solution after a while props.list is the initial list passed from astro previously I will have to add onMount(loadMorePosts) to load initial posts
import { For, Show, createSignal } from 'solid-js';
import Post from './Post';
import getPosts from './getPosts';

export interface Props {
list: Post[] | undefined | void;
}

export default function Posts(props: Props) {
const [loadedPosts, setLoadedPosts] = createSignal<Post[]>(props.list ?? [], { equals: false });

async function loadMorePosts() {
const currentPosts = loadedPosts();

if (currentPosts.length === 0) {
const newPosts = await getPosts(-1);

if (typeof newPosts !== 'undefined')
setLoadedPosts(newPosts);
} else {
const newPosts = await getPosts(currentPosts[currentPosts.length - 1]!.id);

if (typeof newPosts !== 'undefined') {
currentPosts.push(...newPosts);
setLoadedPosts(currentPosts);
}
}
}

return (
<div class='min-h-screen'>
<div class='grid grid-cols-1 gap-12 mt-12'>
<Show when={loadedPosts().length !== 0} fallback={<p class='text-primary'>Loading posts...</p>}>
<For each={loadedPosts()}>{Post}</For>
</Show>
<button class='btn btn-primary' onMouseDown={loadMorePosts}>Load more posts...</button>
</div>
</div>
);
}
import { For, Show, createSignal } from 'solid-js';
import Post from './Post';
import getPosts from './getPosts';

export interface Props {
list: Post[] | undefined | void;
}

export default function Posts(props: Props) {
const [loadedPosts, setLoadedPosts] = createSignal<Post[]>(props.list ?? [], { equals: false });

async function loadMorePosts() {
const currentPosts = loadedPosts();

if (currentPosts.length === 0) {
const newPosts = await getPosts(-1);

if (typeof newPosts !== 'undefined')
setLoadedPosts(newPosts);
} else {
const newPosts = await getPosts(currentPosts[currentPosts.length - 1]!.id);

if (typeof newPosts !== 'undefined') {
currentPosts.push(...newPosts);
setLoadedPosts(currentPosts);
}
}
}

return (
<div class='min-h-screen'>
<div class='grid grid-cols-1 gap-12 mt-12'>
<Show when={loadedPosts().length !== 0} fallback={<p class='text-primary'>Loading posts...</p>}>
<For each={loadedPosts()}>{Post}</For>
</Show>
<button class='btn btn-primary' onMouseDown={loadMorePosts}>Load more posts...</button>
</div>
</div>
);
}
Can you check if there's any problem here
Maciek50322
Maciek503223mo ago
seems fine I wouldn't worry about creating new list in order to save memory, it does single copy, and then the old lists are unused, so they are garbage collected at some point. Also this is shallow copy, you don't create the copies of every element in list, they are the same objects that are in old list, only references are copied (or pointers if you are familiar) to the new list - it really doesn't take much space.
Reve
Reve3mo ago
btw what is untrack in your code?
Maciek50322
Maciek503223mo ago
createMemo is recalculated everytime one of it's dependecies changes. Dependencies are the signals used inside function. It recalculates when loadedPosts() are changed, but I don't want it to recalculate after startId changes. So createMemo ignores signals that are inside untrack function, it's a shorthand for untrack(() => startId())
Reve
Reve3mo ago
It works the same for all effect hooks like createEffect and createRenderEffect right?
Maciek50322
Maciek503223mo ago
yes also simple functions, no need to write const x = createMemo(() => { ... }), can be just const x = () => { ... }, but then the result is not cached, and always updates wherever x is used
Maciek50322
Maciek503223mo ago
have you seen tutorial of solidjs? it's interactive and gives quite some depth on how everything works https://www.solidjs.com/tutorial/introduction_basics
SolidJS
Solid is a purely reactive library. It was designed from the ground up with a reactive core. It's influenced by reactive principles developed by previous libraries.
Reve
Reve3mo ago
I learn faster by just looking at the docs lol I saw it but never actually tried it I'm using Solid with Astro to build some sites This is my first time using Solid since most of the time I build backend libraries
Maciek50322
Maciek503223mo ago
this tut was fun for me, so I recommend it
Want results from more Discord servers?
Add your server