I have an app written with the NextJS app router. On a page (server component), I use a server action to get all the admin accounts from my database via my API, and after that, I display them in a table (client component). This works as expected.
On each row in the table, I have an action that lets the user set the account for that row to the status 'inactive' (default status is active). This also happens via my API, this also works but when the server action to do this is called, the whole layout (root-layout, navigation bar, sub-layout, page) gets re-rendered.
This is not what I want. I would like for just the table to be re-rendered.
Is this possible?
Server actions
export async function getAllAdminAccounts(searchParameters: FormatPageProps) {
const user = await getCurrentUser();
const { skip, take, search, columns, sortOrder } =
formatSearchParameters<AdminAccountType>(searchParameters);
const response = await fetch(
`${process.env.NEXT_PUBLIC_API_URL}/users/All/?Search=${search}&Skip=${skip}&Take=${take}&Columns=${columns}&SortOrders=${sortOrder}`,
{
cache: "no-cache",
next: { tags: ["get-admin-accounts"] },
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + user?.accessToken,
},
}
);
const data: AdminAccountsType = await response.json();
return {
adminAccounts: data.data,
totalCount: data.totalCount,
take,
};
}
export async function updateAdminAccountStatus(id: string) {
const user = await getCurrentUser();
if (!id) return;
await fetch(`${process.env.NEXT_PUBLIC_API_URL}/users/Status/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer " + user?.accessToken,
},
});
revalidateTag("get-admin-accounts");
}
Admin accounts page
async function AdminAccountsAdministrationPage({
searchParams,
}: IndexPageProps) {
const { adminAccounts, totalCount, take } = await getAllAdminAccounts(
searchParams
);
const pageCount = Math.ceil(totalCount / take);
return (
<div className="space-y-6">
<div>
<AdminAccountsTable data={adminAccounts} pageCount={pageCount} />
</div>
</div>
);
}
export default AdminAccountsAdministrationPage;
Admin accounts table
export function AdminAccountsTable({
data,
pageCount,
}: AdminAccountsTableProps) {
const [isPending, startTransition] = React.useTransition();
// Memoize the columns so they don't re-render on every render
const columns = React.useMemo<ColumnDef<AdminAccountType>[]>(
() => [
{
accessorKey: "isActive",
header: ({ column }) => (
<DataTableColumnHeader column={column} title="Status" />
),
meta: { title: "Status" },
cell: ({ row }) => {
const isActive = row.original.isActive;
if (isPending) {
return null;
}
return isActive === true ? (
<Badge variant="default">
<span className="capitalize text-sm">Active</span>
</Badge>
) : (
<Badge variant="destructive">
<span className="capitalize text-sm">Inactive</span>
</Badge>
);
},
filterFn: (row, id, value) => {
return value instanceof Array && value.includes(row.getValue(id));
},
},
{
id: "actions",
cell: ({ row }) => (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
aria-label="Open menu"
variant="ghost"
className="flex h-8 w-8 p-0 data-[state=open]:bg-muted"
>
<DotsHorizontalIcon className="h-4 w-4" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-[160px]">
<DropdownMenuItem
onClick={() => {
startTransition(() =>
updateAdminAccountStatus(row.original.id)
);
}}
disabled={isPending}
>
Delete
<DropdownMenuShortcut>⌘⌫</DropdownMenuShortcut>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
),
},
],
[isPending]
);
return (
<DataTable
columns={columns}
data={data}
pageCount={pageCount}
/>
);
}