Nextjs suspense showing 'old UI' before updating

474 Views Asked by At

In my Nextjs application I am using suspense on a page route to display a loading skeleton until the content is ready.

The url is something like '/organization/:orgId/activity'.

I am using server components and fetching data from a planetscale database using Prisma in my ActivityList component.

If I go from one ..orgId/activity to another ..orgId/activity the following happens:

  1. I see the skeleton loader that is the suspense fallback
  2. I see the old activity list UI from the previous page
  3. The old UI pops out and the new activity list is displayed.

And this keeps happening every time I navigate between the two routes (I also have the same issue with another component as well)

Here is the page component code:

const ActivityPage = async ({ params }: Props) => {
  const isSubscriped = await checkSubscription()
  

  return (
    <div className="w-full space-y-2">
      <Info isSubscriped={isSubscriped} />
      <Separator />
      <Suspense key={params.organizationId} fallback={<ActivityList.Skeleton />}>
        <ActivityList />
      </Suspense>
    </div>
  )
}

export default ActivityPage

And here is the ActivityList component with the skeleton loader code:

import { ActivityItem } from "@/components/activity-item"
import { Skeleton } from "@/components/ui/skeleton"
import { db } from "@/lib/db"
import { auth } from "@clerk/nextjs"
import { redirect } from "next/navigation"


export const ActivityList = async () => {
  const { orgId } = auth()
  
  if (!orgId) {
    redirect("select-org")
  }

  const logs = await db.auditLog.findMany({
    where: {
      orgId,
    },
    orderBy: {
      createdAt: "desc"
    }
  })

  return (
    <ol className="space-y-4 mt-4">
      {logs.length ? (
        logs.map((log) => <ActivityItem key={log.id} auditLog={log} />)
      ) : (
        <p className="block text-muted-foreground text-center text-sm">No activity found in this organization</p>
      )}
    </ol>
  )
}

ActivityList.Skeleton = function() {
  return (
    <ol className="space-y-4 mt-4">
      <div className="w-[75%] flex items-start gap-2">
        <Skeleton className="w-8 h-8 bg-neutral-200" />
        <Skeleton className="w-[75%] h-8 bg-neutral-200"/>
      </div>
      <div className="w-[75%] flex items-start gap-2">
        <Skeleton className="w-8 h-8 bg-neutral-200" />
        <Skeleton className="w-[60%] h-8 bg-neutral-200"/>
      </div>
      <div className="w-[75%] flex items-start gap-2">
        <Skeleton className="w-8 h-8 bg-neutral-200" />
        <Skeleton className="w-[75%] h-8 bg-neutral-200"/>
      </div>
      <div className="w-[75%] flex items-start gap-2">
        <Skeleton className="w-8 h-8 bg-neutral-200" />
        <Skeleton className="w-[55%] h-8 bg-neutral-200"/>
      </div>
      <div className="w-[75%] flex items-start gap-2">
        <Skeleton className="w-8 h-8 bg-neutral-200" />
        <Skeleton className="w-[80%] h-8 bg-neutral-200"/>
      </div>
    </ol>
  )
}

What I would expect to happen is that the skeleton loader keeps showing until the new activity list is ready to be displayed.

I have tried moving the fetching to the page component, I have tried returning null if logs are not yet defined I have tried looking for answers online without any luck I have tried waiting for a new Promise that got resolved after 1 sec which does hide the ugly pop-in. But this caused Hydration errors and also was not really a solution.

Any insight or help would be greatly appreciated.

Thanks

Edit: Thanks to @Jung hyeonuk for helping me find the answer. Turns out that Clerk organization id was not updated yet, which caused the issue.

Added the setActive method on route change fixed the issue!

const onRouteChange = async (href: string) => {
await setActive?.({organization: organization.id})
router.push(href)

}

2

There are 2 best solutions below

0
On

Try adding a className: 'global' at the outermost div of your code and it will work.

    export default function Loading() {
  return <h2 className="global text-black dark:text-white">Loading...</h2>;

}

0
On

I followed the same tutorial and encountered the same problem.This is because pushing to the /org/${orgId} route doesn't update the current orgId, and when I reload the page it shows the old organization's data. Only after loading the new data does it return the correct information, thus creating a “bug”. The setActive function of the useOrganizationList() hook allows you to configure the correct organization before your router.push()