1

I started building a Next.js frontend for a WordPress website, using the App Router and GraphQL plugin to fetch data from WordPress. The homepage is all set up to display the latest posts, and it's a server component.

import PostCard from "./components/post";

import styles from './page.module.scss'

interface Post {
    title: string;
    date: string;
    excerpt: string;
    featuredImage: {
        node: {
          sourceUrl: string;
        };
    };
    categories: {
        edges: {
            node: {
                name: string;
            };
        }[];
    };
    slug: string;
    author: {
        node: {
            name: string;
        };
    };
}

async function getPosts(cursor: string): Promise<Post[]> {
    const endpoint = process.env.WORDPRESS_API_URL || '';

    const response = await fetch(endpoint, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify({
            query: `
                query GetPosts($cursor: String!) {
                    posts(first: 10, after: $cursor) {
                        nodes {
                            title
                            date
                            excerpt
                            featuredImage {
                                node {
                                    sourceUrl
                                }
                            }
                            categories {
                                edges {
                                    node {
                                        name
                                    }
                                }
                            }
                            slug
                            author {
                                node {
                                    name
                                }
                            }
                        }
                    }
                }
            `,
            variables: {
                cursor: cursor,
            },
        }),
    });

    const data = await response.json();

    return data.data.posts.nodes;
}

export default async function HomePage() {

    const posts = await getPosts("");

    return (
        <div>
            <div className={styles.postsList}>
                    {posts.map((post) => (
                        <PostCard post={post} key={post.slug}/>
                    ))}
            </div>
            <button onClick={ }>Load More</button>
        </div>
    );
}

The challenge is loading the next batch of posts right after this cursor (which is ID of the last post I loaded by GraphQL) without using React's useState or useEffect hooks, as these do no work on server components.

How can I make the "Load More" button work with server components?

I've tried to tackle pagination using query strings, but that plan fizzled out because GraphQL's WordPress plugin exclusively works with cursor-based pagination.

Danziger
  • 19,628
  • 4
  • 53
  • 83
Ioan
  • 25
  • 4

1 Answers1

0

Server components and client components can be combined in the same component tree, which is what you should be doing here: Separate the server part from the client part in two separated components.

Most of what you posted is the server part, and remains mostly the same, with some minor changes to use the client component inside HomePage:

export default async function HomePage() {

  const posts = await getPosts("");

  return (
    <div>
      <div className={styles.postsList}>
        { posts.map((post) => (
          <PostCard post={ post } key={ post.slug } />
        )) }
      </div>
      
      <MorePosts>
    </div>
  );

}

Then, you'll have the client component, which would look something like this:

'use client'

export const MorePosts = () => {

  // This is just some placeholder / pseudo-code for whatever custom logic
  // you need to fetch these:

  const { morePosts, loadMorePosts, areMorePostsLoading } = useMorePosts();

  return (
    <>
      <div className={ styles.morePostsList }>
        { morePosts.map((post) => (
          <PostCard post={ post } key={ post.slug } />
        )) }
      </div>

      <button
        onClick={ loadMorePosts }
        disabled={ areMorePostsLoading }>
        Load More
      </button>
    </>
  )
}

Note useMorePosts() represents a custom hook that probably uses useEffect and useState plus fetch, axios or some other client-side request library to load additional posts in pages / batches and store them, plus manage the associated status (loading, error, cursor position...).

Danziger
  • 19,628
  • 4
  • 53
  • 83
  • 1
    that was the solution. Thanks! – Ioan Aug 18 '23 at 12:00
  • @Danziger How can I include the page number in page route? or consider I am setting some queries to my API endpoint and I want to include them too. `EX: example.com/posts?page=1&author=name` – Arad Aug 28 '23 at 17:40
  • 1
    @Arad You can use [dynamic routes](https://nextjs.org/docs/pages/building-your-application/routing/dynamic-routes). Your route (pathname) would look something like this: `pages/posts/[page].js` and then you can use [search params](https://nextjs.org/docs/app/api-reference/file-conventions/page#searchparams-optional) for any additional filter. – Danziger Aug 28 '23 at 18:11