Next.js Discord

Discord Forum

`React.cache` doesn't work when using PPR

Answered
Asian paper wasp posted this in #help-forum
Open in Discord
Asian paper waspOP
So I have specified a query as follow:

// queries.ts

'use server';

import { cache as reactCache } from 'react';

export const getBlogsMetadataByIds = reactCache(async (ids: string[]) => {
  noStore();
  const blogsMetadata = await prisma.blogMetadata.findMany({
    where: { id: { in: ids } },
    include: { _count: { select: { likes: true } } },
  });

  return blogsMetadata.map(({ _count, ...rest }) => ({
    ...rest,
    like: _count.likes,
  }));
});


And then I use it in Views

// views.tsx
export const Views: FC<ViewsProps> = async ({
  blogIds,
  blogId,
  ...props
}) => {
  const metadata =(await getBlogsMetadataByIds(blogIds)).find(({ id }) => id === blogId)

  return (
    <Typography aria-label="Blog views" startDecorator={<Eye />} {...props}>
      {numberFormatter.format(metadata?.view ?? 0)}
    </Typography>
  );
};


In which, the Views component is being used in the blog listing page.
// /app/blog/page.tsx

const Blogs: FC = async () => {
  // yes, blogs and blogMetadata are stored in two different places. Hence 2 seperated API calls.
  const blogs = await getBlogs({ page: 1 });

  return (
    <div>
      {blogs.map(({ id }) => (
        <Card>
          // using PPR here
          <Suspense fallback={<ViewsSkeleton />}>
            <Views
              blogId={id}
              blogIds={blogs.map(({ id }) => id)}
            />
          </Suspense>
        </Card>
      ))}
    </div>
  )
}


The idea is to:
1. DB is queried only once in the blog listing page
2. The result of getBlogsMetadataByIds is NOT cached in other server requests. i.e. when the user refreshes the page or other users visit the same page, it should see the up to date metadata
Answered by Asian paper wasp
So instead of

const Blogs: FC = async () => {
  // yes, blogs and blogMetadata are stored in two different places. Hence 2 seperated API calls.
  const blogs = await getBlogs({ page: 1 });

  return (
    <div>
      {blogs.map(({ id }) => (
        <Card>
          // using PPR here
          <Suspense fallback={<ViewsSkeleton />}>
            <Views
              blogId={id}
              blogIds={blogs.map(({ id }) => id)}
            />
          </Suspense>
        </Card>
      ))}
    </div>
  )
}

which creates a new blogIds everytime, I should do

const Blogs: FC = async () => {
  // yes, blogs and blogMetadata are stored in two different places. Hence 2 seperated API calls.
  const blogs = await getBlogs({ page: 1 });
  const blogIds = blogs.map(({ id }) => id)

  return (
    <div>
      {blogs.map(({ id }) => (
        <Card>
          // using PPR here
          <Suspense fallback={<ViewsSkeleton />}>
            <Views
              blogId={id}
              blogIds={blogIds}
            />
          </Suspense>
        </Card>
      ))}
    </div>
  )
}
View full answer

4 Replies

Asian paper waspOP
This used to, and in theory should work, because React will invalidate the cache for all memoized functions for each server request. See https://react.dev/reference/react/cache#caveats

Yet, for whatever reasons, currently the DB is being queried every time the function is called as if React.cache is not used at all.
Tested by adding a console.log in getBlogsMetadataByIds. The message shouldn't log multiple times if React.cache is functioning.
Asian paper waspOP
OK... the issue is
Asian paper waspOP
So instead of

const Blogs: FC = async () => {
  // yes, blogs and blogMetadata are stored in two different places. Hence 2 seperated API calls.
  const blogs = await getBlogs({ page: 1 });

  return (
    <div>
      {blogs.map(({ id }) => (
        <Card>
          // using PPR here
          <Suspense fallback={<ViewsSkeleton />}>
            <Views
              blogId={id}
              blogIds={blogs.map(({ id }) => id)}
            />
          </Suspense>
        </Card>
      ))}
    </div>
  )
}

which creates a new blogIds everytime, I should do

const Blogs: FC = async () => {
  // yes, blogs and blogMetadata are stored in two different places. Hence 2 seperated API calls.
  const blogs = await getBlogs({ page: 1 });
  const blogIds = blogs.map(({ id }) => id)

  return (
    <div>
      {blogs.map(({ id }) => (
        <Card>
          // using PPR here
          <Suspense fallback={<ViewsSkeleton />}>
            <Views
              blogId={id}
              blogIds={blogIds}
            />
          </Suspense>
        </Card>
      ))}
    </div>
  )
}
Answer