Next.js Discord

Discord Forum

Populate useState constant with server-side data fetch.

Unanswered
Fire ant posted this in #help-forum
Open in Discord
Original message was deleted.

11 Replies

Fire ant
Hey guys, I am creating a blog using NextJS and contentful, and I have the following question:

How can I fetch the blog posts on server side and use this fetched data on my state?

Features
1 - Search: users can search in an input and then a debounced function is called, navigating the user to the route:
/blog?search={searchState}

2 - Load more posts: Users can click in a load more button, where I am handling with pagination.

The problem is that, once I need this load more(Client side) functionalitty, I need to keep the blog posts in a state. On the first render, the data is loaded and populated correctly at useState, but, If I search and navigate to a route with a different search, it does not update the value of blog posts.
blog/page.tsx

import BlogClientPage from '@/components/pages/BlogClientPage'
import { env } from '@/env'
import { PAGE_SIZE } from '@/utils/pagination'

async function onGetPosts(search: string) {
  const response = await fetch(
    `https://cdn.contentful.com/spaces/${spaceId}/environments/${environment}/entries?content_type=pageBlogPost&fields.title[match]=${search || ''}&limit=${PAGE_SIZE}`,
    {
      headers: {
        Authorization: `Bearer ${env.NEXT_PUBLIC_CONTENTFUL_API_KEY}`,
      },
      cache: 'no-cache',
    },
  )

  const data = await response.json()

  return data
}

export default async function BlogPostPage({
  searchParams,
}: {
  searchParams: { search: string }
}) {
  const data = await onGetPosts(searchParams.search)

  return <BlogClientPage searchText={searchParams.search} data={data} />
}
BlogClientPage.tsx

export interface BlogClientPageProps {
  data: BlogPostProps
  searchText: string
}

export default function BlogClientPage({
  data,
  searchText,
}: BlogClientPageProps) {
  const [isPendingShowMore, startShowMoreTransition] = useTransition()

  const [search, setSearch] = useState<string>(searchText || '')
  const [blogData, setBlogData] = useState<BlogPostProps>(data)

  const { push } = useRouter()

  const currentPage = blogData.skip / PAGE_SIZE

  const skip = currentPage * PAGE_SIZE

  const showShowMoreButton = blogData.items.length < blogData.total

  function onUpdatePage() {
    setBlogData((prev) => ({
      ...prev,
      skip: prev.skip + PAGE_SIZE,
    }))
  }

  async function onLoadMorePosts() {
    startShowMoreTransition(async () => {
      onUpdatePage()

      const response = await fetch(
        `https://cdn.contentful.com/spaces/${spaceId}/environments/${environment}/entries?content_type=pageBlogPost&fields.title[match]=${search}&skip=${skip + PAGE_SIZE}&limit=${PAGE_SIZE}`,
        {
          headers: {
            Authorization: `Bearer ${env.NEXT_PUBLIC_CONTENTFUL_API_KEY}`,
          },
        },
      )

      const data = await response.json()

      console.log('data', data)

      setBlogData((prev) => ({
        ...prev,
        items: [...prev.items, ...data.items],
        includes: {
          Asset: [...prev.includes.Asset, ...data.includes.Asset],
          Entry: [...prev.includes.Entry, ...data.includes.Entry],
        },
      }))
    })
  }

  const onSearch = debounce(async () => {
    push(`/blog?search=${search}`)
  }, 500)

  useEffect(() => {
    onSearch()

    return () => onSearch.cancel()
  }, [search])

  useEffect(() => {
    setBlogData(data)
  }, [data])


IF I use this:
  useEffect(() => {
    setBlogData(data)
  }, [data])
The data is always correctly updated, but it does not seem to be correct. I don't know if it makes sense to have a useEffect to update the state after fetching data on server-side.

Obs:
1 - If I log the received data param received from the server-side fetch call, it always logs the correct data, but the blogData state is not being recalculated without the useEffect hook.
Tonkinese
convert onGetPosts to a server function. Call it in your client. Set the response to the existing state
its because your state is on the client and your client doesn't actively know if new data from a rsc gets streamed in
so watching for your passed prop from a rsc and setting the state to the new data fixes this
i kinda wish react would do that automatically, maybe in future updates....
Fire ant
Okay, thank you a lot guys! I was wondering if I was doing something wrong or not that performatic, but if its like it is, its ok
Thanks!