Next.js Discord

Discord Forum

Best practice for refetching?

Answered
Selkirk Rex posted this in #help-forum
Open in Discord
Selkirk RexOP
Currently have a setup where I have a server component as page.tsx where I call the necessary data, and pass to a client component. Similar to this:
export const dynamic = 'force-dynamic';

import OverviewPage from 'app/[lng]/overview/OverviewPage';
import { getMeltStations } from 'app/utils/getMeltStations';

const Page = async () => {
  const meltStations = await getMeltStations();

  return <OverviewPage meltStations={meltStations} />;
};

export default Page;


OverviewPage.tsx would be 'use client'.

What are the best practices for refetching from the client component? Should I be using revalidateTag or am I going up the wrong tree? I don't want to do a reload of the page, as I would like to have UI implying that it's fetching?
Answered by Selkirk Rex
Updating the refetch function by adding 'router.refresh()' solved the issue:
  
const onRefetchProjects = useCallback(async () => {
    await revalidateBuildProjects();
    setFetchDate(new Date());
    router.refresh();
  }, [router]);
View full answer

4 Replies

Asian black bear
Reloading the page is the most idiomatic and convenient way to do this without unnecessary overhead. If you for some reason insist on advanced client-side refetching capabilities you'd have to use react-query, prefetch server-side and initialise the data in the client component while also passing on a fetcher accessing a public endpoint to refetch the data. At this point you should really evaluate whether you REALLY need all of this just because you want a SPA feel despite having a SSRed MPA.
Selkirk RexOP
@Asian black bear I've tried to solve this using revalidateTag. However, I've encountered something that is baffling me, I wonder if you have any insight?

server component - page.tsx:
export const dynamic = 'force-dynamic';
const initialParams: GetBuildProjectsParams = {
  pageNumber: 1,
  pageSize: 25,
  sortBy: 'createdAt',
  sortOrder: SortOrder.DESC,
};

const Page = async ({ searchParams, params: { lng } }: PageProps) => {
  const params: GetBuildProjectsParams = {
    pageNumber: parseInt(searchParams.pageNumber) || initialParams.pageNumber,
    pageSize: parseInt(searchParams.pageSize) || initialParams.pageSize,
    sortOrder: Object.values<string>(SortOrder).includes(searchParams.sortOrder)
      ? (searchParams.sortOrder as SortOrder)
      : initialParams.sortOrder,
    name: searchParams.name || undefined,
  };

  const querySearchParams = new URLSearchParams();

  Object.entries(params).forEach(([key, value]) => {
    if (value !== undefined && canStringify(value)) {
      querySearchParams.append(key, value.toString());
    }
  });

  const query = querySearchParams.toString();

  const res = await fetch(`http://localhost:3000/api/build-projects?${query}`, {
    next: { tags: ['buildProjects'] },
  });

  const buildProjects = (await res.json()) as GetBuildProjectsResponse;

  console.log('FETCHED RESULTS:::::::::::');
  console.log(buildProjects);

  return <BuildFilesPage {...{ buildProjects, params, lng }} />;
};

export default Page;

I then have an action in action.ts:
'use actions'
import { revalidateTag } from 'next/cache';

export async function revalidateBuildProjects() {
  revalidateTag('buildProjects');
}

In my client component I have filters that when updated trigger this:
  const onRefetchProjects = useCallback(async () => {
    console.log('REVALIDATE CLIENT');
    await revalidateBuildProjects();
    setFetchDate(new Date());
  }, []);
Here are my logs:

Initial render:
yarn run v1.22.22
$ next dev
  ▲ Next.js 14.2.5
  - Local:        http://localhost:3000
  - Environments: .env

 ✓ Starting...
 ✓ Ready in 1719ms
 ✓ Compiled /middleware in 461ms (931 modules)
 ○ Compiling /[lng]/test ...
 ✓ Compiled /[lng]/test in 5.1s (5924 modules)
 ○ Compiling /api/build-projects ...
 ✓ Compiled /api/build-projects in 1510ms (3552 modules)
 GET /api/build-projects?pageNumber=1&pageSize=25&sortBy=createdAt&sortOrder=desc 200 in 1682ms
FETCHED RESULTS:::::::::::
{
  totalCount: 1,
  result: [
    {
      id: 'a5976cb1-2120-4ed0-80c5-29ac77d2be21',
      externalId: 'fa7c2cce-d96b-4a14-a623-be434a831efb',
      activeRevision: 1,
      obsoleted: false,
      createdAt: '2024-08-06T15:15:59.513Z',
      deletedAt: null,
      build: [Object]
    }
  ]
}
 GET /enUS/test 200 in 7652ms

After updating filters and triggering revalidateTag()
 GET /api/build-projects?pageNumber=1&pageSize=25&sortBy=createdAt&sortOrder=desc&name=Seb 200 in 30ms
 GET /api/build-projects?pageNumber=1&pageSize=25&sortBy=createdAt&sortOrder=desc&name=Seb 200 in 24ms
FETCHED RESULTS:::::::::::
{ totalCount: 0, result: [] }
 POST /enUS/test?pageNumber=1&pageSize=25&sortBy=createdAt&sortOrder=desc&name=Seb 200 in 223ms
FETCHED RESULTS:::::::::::
{ totalCount: 0, result: [] }

The above logs clearly show that the server is fetching properly but not updating client. I have a button that triggers the onRefetchProjects function again, after this, the data is correctly fetched and rendered in client.
FETCHED RESULTS:::::::::::
{ totalCount: 0, result: [] }
 POST /enUS/test?pageNumber=1&pageSize=25&sortBy=createdAt&sortOrder=desc&name=Seb 200 in 50ms

Do you have any idea what's happening?
Selkirk RexOP
Updating the refetch function by adding 'router.refresh()' solved the issue:
  
const onRefetchProjects = useCallback(async () => {
    await revalidateBuildProjects();
    setFetchDate(new Date());
    router.refresh();
  }, [router]);
Answer