1

Problem

How to implement pagination for components in a page without using query parameters?

Context

Just started learning Sveltekit (new to SSR in general) - decided to try and build an "admin dashboard". Dashboard page has two tables - one that displays posts and the other displays users.

What I can't figure out is how can I implement pagination (and search/sort)?

I was hoping to instead access the pagination value from +page.svelte in the load() function in +page.server.ts somehow but not sure what the best approach is....

// +page.server.ts
export async function load({ fetch }) {
        // how to pass limit and skip here from the client so that it can be used in the URL
        // e.g. https://dummjson.com/posts?skip={skip}&limit={limit}
    const postsResponse = await fetch('https://dummyjson.com/posts').then((result) => result.json());
    const usersResponse = await fetch('https://dummyjson.com/posts').then((result) => result.json());

    return {
        posts: postsResponse.posts,
        totalPosts: postsResponse.total,
        users: usersResponse.users,
        totalUsers: usersResponse.total
    };
}

<!-- +page.svelte -->
<script>
    import PostsTable from './components/PostsTable.svelte'
    import UsersTable from './components/UsersTable.svelte'

    export let data;
 
    const handlePostsPageChanged = (page: number) => {
           // what to do here?
           // I want the SSR to call the load() function with the page number passed
    }

    const handleUsersPageChanged = (page: number) => {
          // what to do here?
          // I want the SSR to call the load() function with the page number passed
    }
  
</script>

<h1>Welcome!</h1>
<PostsTable posts={data.posts} total={data.totalPosts} onPageChanged={handlePostsPageChanged} />
<UsersTable users={data.users} total={data.totalUsers} onPageChanged={handleUsersPageChanged} />

Am I even thinking about this correctly?

What I tried

I tried using cookies with form actions and that worked but feels like a rather hacky solution.

query params are not a preferable solution as then the url would look something like dashboard/skip_posts=30&limit_posts=30&skip_users=10&limit_users=30 which doesn't make sense because why does the dashboard have pagination? its the tables being paginated not the dashboard page.

Also, query-params would pollute the browser history (user tries to click back and they stay on the dashboard but the tables page changes which is unexpected UX)

Finally, instead of making the server do the fetch, I just fetch the data on the client but doesn't that defeats the purpose of SSR?

  • if you want SSR, how would you reflect that the system should render for example page 5 ? query parameters are the most common way to solve this problem, alternatively you can only render the first page and then fetch the other page client side as you mention – Stephane Vanraes May 31 '23 at 07:09

1 Answers1

0

This is my implementation without cookies or query parameters for this matter.

At this point I have to clear out that SvelteKit names the page parameters as params too, sensibly but confusingly.

So I will be using two params, limit and skip for my pagination. I use Mongodb but you can adjust your own solution:

myroute/[limit]/[skip]/+page.server.js:

import * as db from '$lib/server/db'

/** @type {import('./$types').PageServerLoad} */
export async function load({params}) {
    const documents = await db.documents.aggregate([
        {$set: {_id: {$toString: '$_id'}}}, 
        {$limit: Number(params.limit) + Number(params.skip)},
        {$skip: Number(params.skip)},
    ]).toArray();
    return {documents};
};

It is possible that the parameters cast by SvelteKit to the appropriate type, I have not tried it yet:

https://kit.svelte.dev/docs/advanced-routing#matching

Then the component will be as follows, where Table is some component that presents the data with some pagination, in which the pagination will simply increase or decrease the skip by limit, or it will allow for several or custom limits.

When the limit or skip value changes, the page reacts and calls goto, which updates params without obvious change on the UI with the well known Svelte magic.

my route/[limit]/[skip]/+page.svelte:

<script>
    import { goto } from '$app/navigation';
    import { page } from '$app/stores';
    import Table from '...';
    import { onMount } from 'svelte';

    /** @type {import('./$types').PageData} */
    export let data;

    /**
     * Page limit
     * @type {number}
     */
    let limit = Number($page.params.limit);

    /**
     * Page skip
     * @type {number}
     */
    let skip = Number($page.params.skip);

    /**
     * Allow navigation only after mount.
     * @link https://github.com/sveltejs/kit/discussions/3245
     * @type {boolean}
     */
    let mounted = false;
    onMount(() => mounted = true);

    $: mounted && goto(`/myroute/${limit}/${skip}`);
</script>

<Table 
    bind:limit
    bind:skip
    total={...}
    data={...}>
</Table>
Wtower
  • 18,848
  • 11
  • 103
  • 80