douglasrcjames
douglasrcjames

Reputation: 1665

Next.js 13 pattern for changeable API fetch request

I have a seemingly simple Next.js app directory pattern where I want the ability for a user to cycle through pages of data fetched from the server. Take a simple example below, where I want the user-changeable state of currentPage to be passed to my article fetch request. What is the best practice for this pattern? The current setup below will cause an infinite loop issue because listArticles needs to be called in a server component, but then I cannot pass a dynamic page value to the fetch request. How do I pass this page to the fetch request?

page.tsx

// Parent
export default async function Page() {
    const [currentPage, setCurrentPage] = useState(0);
    return (
        <>
            <h3>Articles</h3>
            <ArticleList
                currentPage={currentPage}
            />

            <button onClick={() => setCurrentPage(currentPage - 1)}>Previous page</button>
            <p>Current page: {currentPage}</p>
            <button onClick={() => setCurrentPage(currentPage + 1)}>Next page</button>
        </>
    )
}

ArticleList.tsx

// Child server component
export default async function ArticleList({
    page = 0,
}: {
    page?: number;
}) {
    const listArticles = async () => {
        const res = await fetch(`${process.env.NODE_ENV === "development" ? SITE.URLS.TEST : SITE.URLS.LIVE}/api/articles?&page=${page}`, {
            method: "GET",
            next: { revalidate: 5 },
        });
        
        if (!res.ok) {
            console.error("Error fetching articles:");
            console.error(res);
        } else {
            return res.json();
        }
    }
    
    let data = await listArticles();

    return (
        <>
            { 
                data.articles.map((article: any) => (
                    <div key={article.id}>
                        {article.id}
                    </div>
                ))
            }
        </>
    )
}

Upvotes: 0

Views: 392

Answers (1)

Fabio Nettis
Fabio Nettis

Reputation: 2863

The way you would go about this is by persisting the current page inside the query and then using the <Link /> component to redirect the user to the correct url. This allows you to completely renounce client components for this specific use case.


Very simple implementation example

As you see I use nullish coalescing in when parsing the current page, this ensures that the currentPage variable is always 1 if no page was provided.

import Link from "next/link";

export default async function Page(props) {
  const params = props.searchParams;
  const currentPage = parseInt(params.get("page") ?? "0", 10);
  return (
    <>
      {/* Rest of your JSX */}

      <ArticleList currentPage={currentPage} />

      <Link href={{ pathname: "/articles", query: { page: currentPage - 1 } }}>
        <button>Previous page</button>
      </Link>

      <Link href={{ pathname: "/articles", query: { page: currentPage + 1 } }}>
        <button>Next page</button>
      </Link>

      {/* Rest of your JSX */}
    </>
  );
}

Also I see an error in the initial code snippet you have provided, you can not use hooks (like useState) inside a server component.

Upvotes: 1

Related Questions