1

I'm building a simple Next13 lottery app to learn more about the new App Router. I want to fetch data on a server component based on a dynamically-changing piece of state and I am not sure how to accomplish this.

LottosPage is a server component that displays a grid of interactive LottoCard client components. Following the new Next13 Server Components, it is implemented as an async function that fetches the data on the server:

app/lottos/page.tsx:

export default async function LottosPage() {
  const games: Game[] = await getGames(chainId);

  // process and map games to LottoCards 
}

When a user interacts with the LottoCards (e.g. buys a ticket), I refresh the route (using router.refresh()) prompting the LottosPage to refetch the data resulting in an update of the games shown. The thing is I want it to fetch different data based on the selected chainId. chainId is selected dynamically in a dropdown in the layout.tsx file and I pass it down the component tree using a context provider, this gives all the child components access to its dynamic value using a useContext() hook. The problem is I can't use react hooks in server components. So I am not sure how to access the chainId in LottosPage.

So, to summarize, what I want it do is fetch different data based on the selected chainId (as shown in the snippet above). And I want the LottosPage to refetch the new data when the selected chainId changes. Is there anyway to accomplish this while keeping LottosPage a server component? My intuition tells me it's possible because the actual component itself has no dynamic or interactive elements. It just needs to be completely re-rendered on the server. This is my first Next app and I pretty new to web development so I'm open to any suggestions, I might be thinking about this the wrong way.

ivanatias
  • 3,105
  • 2
  • 14
  • 25
Adham
  • 121
  • 7

1 Answers1

4

Server components aren't interactive, but you can achieve what you need with the searchParams prop on LottosPage, and useSearchParams hook & Link component on the dropdown component.

A basic example:

Dropdown component

'use client'

import Link from 'next/link'
import { useSearchParams } from 'next/navigation'

const Dropdown = () => {
  const searchParams = useSearchParams()

  const createQueryString = (key: string, value: number | string) => {
    const params = new URLSearchParams(searchParams)
    params.set(key, String(value))

    return String(params)
  }

  return (
    <div>
      <Link href={`/lottos?${createQueryString('chainId', 100)}`}>
        Lotto 100
      </Link>
      <Link href={`/lottos?${createQueryString('chainId', 200)}`}>
        Lotto 200
      </Link>
      <Link href={`/lottos?${createQueryString('chainId', 300`}>
        Lotto 300
      </Link>
    </div>
  )
}

export default Dropdown

Lottos page

interface Props {
  searchParams?: { chainId?: string }
}

const LottosPage = async ({ searchParams }: Props) => {
  const chainId = searchParams?.chainId ?? ''
  const games: Game[] = await getGames(chainId)

  // process and map games to LottoCards 
}

export default LottosPage

As stated in the docs, using searchParams will opt the page into dynamic rendering at request time.

ivanatias
  • 3,105
  • 2
  • 14
  • 25
  • 1
    Thanks! I came across this but wasn't sure if it was the right approach. This completely removes the need for using the chainId state and the context provider since now client components can just access it using useSearchParams.get() – Adham Mar 25 '23 at 16:13