-
Notifications
You must be signed in to change notification settings - Fork 50.8k
Open
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugA potential issue that we haven't yet confirmed as a bug
Description
When a server component is wrapped with Suspense tag, revalidatePath does not work correctly.
Only when npm run build && npm run start
React version: 19.2.4
Next.js version: 16.2.0
Steps To Reproduce
- Wrap a Server component (list of item) with Suspense tag
npm run build && npm run start- Delete an item with the UI button (will delete and revalidatePath)
- See not UI update after the request / response
Link to code example:
I tried to reproduce the bug on CodeSandbox, but i think the cache / revalidatePath does not work as my own machine. Sorry.
The code is maybe not the best, i did the Acme course on NextJS 16.
If there is some better pratice, i will be glad to learn them. (except the interaction with Data, i will do an API for that i think. Server action will retrieve some data from an external API)
// app/dashboard/invoices/page.tsx
import Pagination from '@/app/ui/invoices/pagination';
import Search from '@/app/ui/search';
import Table from '@/app/ui/invoices/table';
import { CreateInvoice } from '@/app/ui/invoices/buttons';
import { lusitana } from '@/app/ui/fonts';
import { InvoicesTableSkeleton } from '@/app/ui/skeletons';
import { Suspense } from 'react';
import { fetchInvoicesPages } from '@/app/lib/data';
export default async function Page(props: { searchParams?: Promise<{ query?: string; page?: string }> }) {
const searchParams = await props.searchParams;
const query = searchParams?.query || '';
const pageParameter = Number.parseInt(searchParams?.page || '1', 10);
const currentPage = Number.isNaN(pageParameter) || pageParameter < 1 ? 1 : pageParameter;
const totalPages = await fetchInvoicesPages(query);
return (
<div className="w-full">
<div className="flex w-full items-center justify-between">
<h1 className={`${lusitana.className} text-2xl`}>Invoices</h1>
</div>
<div className="mt-4 flex items-center justify-between gap-2 md:mt-8">
<Search placeholder="Search invoices..." />
<CreateInvoice />
</div>
<Suspense key={query + currentPage} fallback={<InvoicesTableSkeleton />}>
<Table query={query} currentPage={currentPage} />
</Suspense>
<div className="mt-5 flex w-full justify-center">
<Pagination totalPages={totalPages} />
</div>
</div>
);
}// app/ui/invoices/table.tsx
import Image from 'next/image';
import { UpdateInvoice, DeleteInvoice } from '@/app/ui/invoices/buttons';
import InvoiceStatus from '@/app/ui/invoices/status';
import { formatDateToLocal, formatCurrency } from '@/app/lib/utils';
import { fetchFilteredInvoices } from '@/app/lib/data';
export default async function InvoicesTable({ query, currentPage }: { query: string; currentPage: number }) {
const invoices = await fetchFilteredInvoices(query, currentPage);
return (
<div className="mt-6 flow-root">
<div className="inline-block min-w-full align-middle">
<div className="rounded-lg bg-gray-50 p-2 md:pt-0">
...
<table className="hidden min-w-full text-gray-900 md:table">
<thead className="rounded-lg text-left text-sm font-normal">
<tr>
<th scope="col" className="px-4 py-5 font-medium sm:pl-6">
Customer
</th>
<th scope="col" className="px-3 py-5 font-medium">
Email
</th>
<th scope="col" className="px-3 py-5 font-medium">
Amount
</th>
<th scope="col" className="px-3 py-5 font-medium">
Date
</th>
<th scope="col" className="px-3 py-5 font-medium">
Status
</th>
<th scope="col" className="relative py-3 pl-6 pr-3">
<span className="sr-only">Edit</span>
</th>
</tr>
</thead>
<tbody className="bg-white">
{invoices?.map((invoice) => (
<tr
key={invoice.id}
className="w-full border-b py-3 text-sm last-of-type:border-none [&:first-child>td:first-child]:rounded-tl-lg [&:first-child>td:last-child]:rounded-tr-lg [&:last-child>td:first-child]:rounded-bl-lg [&:last-child>td:last-child]:rounded-br-lg"
>
<td className="whitespace-nowrap py-3 pl-6 pr-3">
<div className="flex items-center gap-3">
<Image
src={invoice.image_url}
className="rounded-full"
width={28}
height={28}
alt={`${invoice.name}'s profile picture`}
/>
<p>{invoice.name}</p>
</div>
</td>
<td className="whitespace-nowrap px-3 py-3">{invoice.email}</td>
<td className="whitespace-nowrap px-3 py-3">{formatCurrency(invoice.amount)}</td>
<td className="whitespace-nowrap px-3 py-3">{formatDateToLocal(invoice.date)}</td>
<td className="whitespace-nowrap px-3 py-3">
<InvoiceStatus status={invoice.status} />
</td>
<td className="whitespace-nowrap py-3 pl-6 pr-3">
<div className="flex justify-end gap-3">
<UpdateInvoice id={invoice.id} />
<DeleteInvoice id={invoice.id} />
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
);
}// app/ui/invoices/buttons.tsx
...
export function DeleteInvoice({ id }: { id: string }) {
const deleteInvoiceWithId = deleteInvoice.bind(null, id);
return (
<form action={deleteInvoiceWithId}>
<button type="submit" className="rounded-md border p-2 hover:bg-gray-100">
<span className="sr-only">Delete</span>
<TrashIcon className="w-5" />
</button>
</form>
);
}// app/lib/action.ts
...
export async function deleteInvoice(id: string) {
try {
await sql`DELETE FROM invoices WHERE id = ${id}`;
} catch (error) {
console.error(error);
// Add toast for error ?
return;
}
revalidatePath('/dashboard/invoices');
}The current behavior
When deleting an item, the UI is not refreshed.
The expected behavior
With revalidatePath, we should have an UI up to date after deleting an element.
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
Status: UnconfirmedA potential issue that we haven't yet confirmed as a bugA potential issue that we haven't yet confirmed as a bug