I have NextJS 13 app with app router. Which displays a table (shadcn UI) data-table with server side pagination. I have a create button that goes to a create page, And I can create a new entry into the table. On success of the API I do a
router.push("/path")
that goes back to the table page. This shows a new entry in my table. But when I do the same after going to an edit page and update an existing row and do a router push the data doesn't get updated.
Edit Page
import EditBrandForm from "@/components/brand/edit-brand-form";
import { DashboardHeader } from "@/components/header";
import { BASE_API_URL } from "@/constants/constants";
import { BRANDS_ENDPOINT } from "@/constants/routes";
import { getAccessTokenCookie } from "@/lib/session";
import { Brand } from "@/types/brand";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Edit brand",
};
export default async function EditBrand({
params,
}: {
params: { id: number };
}) {
const apiResponse = await fetch(
`${BASE_API_URL}${BRANDS_ENDPOINT}${params.id}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAccessTokenCookie()}`,
},
}
);
const brandResponse: Brand = await apiResponse.json();
return (
<div>
<DashboardHeader heading="Edit brand"></DashboardHeader>
<EditBrandForm brand={brandResponse} />
</div>
);
}
EditBrandForm
"use client";
import { brandFormSchema } from "@/lib/validations/brand";
import { Input } from "../ui/input";
import * as z from "zod";
import * as React from "react";
import { useToast } from "../ui/use-toast";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { brandService } from "@/services/brand.service";
import { useRouter } from "next/navigation";
import { Button } from "../ui/button";
import { Icons } from "../icons";
import { Brand } from "@/types/brand";
type FormData = z.infer<typeof brandFormSchema>;
interface EditBrandFormProps {
brand: Brand;
}
export default function EditBrandForm({ brand }: EditBrandFormProps) {
const [isLoading, setIsLoading] = React.useState<boolean>(false);
const router = useRouter();
const { toast } = useToast();
const defaultValues: FormData = {
name: brand.name,
website: brand.website,
};
const {
register,
handleSubmit,
formState: { errors },
clearErrors,
} = useForm<FormData>({
resolver: zodResolver(brandFormSchema),
defaultValues,
});
async function onSubmit(data: FormData) {
setIsLoading(true);
const apiResponse = await brandService.editBrand(
brand.id,
data.name,
data.website
);
setIsLoading(false);
if (!apiResponse.error) {
router.push("/brands");
} else {
toast({
variant: "destructive",
title: "An unexpected error occured.",
});
}
}
return (
<form
onSubmit={handleSubmit(onSubmit)}
className="px-2 py-8 flex w-full flex-col space-y-6 sm:w-[350px]"
>
<div className="grid w-full max-w-sm items-center gap-1.5">
<Input
{...register("name")}
type="text"
placeholder="Brand name"
onChange={() => {
clearErrors("name");
}}
/>
{errors?.name && (
<p className="px-1 text-xs text-red-600">{errors.name.message}</p>
)}
</div>
<Input type="text" placeholder="Brand website" {...register("website")} />
<Button disabled={isLoading} className="w-1/2">
{isLoading && <Icons.spinner className="mr-2 h-4 w-4 animate-spin" />}
Update
</Button>
</form>
);
}
Data table Page
import { BASE_API_URL } from "@/constants/constants";
import { BrandsTableShell } from "@/components/brands-table-shell";
import CreateButton from "@/components/create-button";
import { DashboardHeader } from "@/components/header";
import { getAccessTokenCookie } from "@/lib/session";
import { Metadata } from "next";
import { BRANDS_ENDPOINT } from "@/constants/routes";
export const metadata: Metadata = {
title: "Brands",
description: "Brands",
};
interface IndexPageProps {
searchParams: {
[key: string]: string | string[] | undefined;
};
}
export default async function DashboardPage({ searchParams }: IndexPageProps) {
const { page, per_page } = searchParams;
// Number of items per page
const pageSize = typeof per_page === "string" ? parseInt(per_page) : 10;
// Current page number
const pageNumber = page === undefined ? 1 : page;
const apiResponse = await fetch(
`${BASE_API_URL}${BRANDS_ENDPOINT}?page=${pageNumber}&size=${pageSize}`,
{
method: "GET",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${getAccessTokenCookie()}`,
},
}
);
const brands = await apiResponse.json();
const pageCount = brands.total_pages;
return (
<div>
<DashboardHeader heading="Brands" text="Create and manage brands">
<CreateButton text="Add brand" route="brands/create" />
</DashboardHeader>
<div className="px-2 py-10">
<BrandsTableShell data={brands.results} pageCount={pageCount} />
</div>
</div>
);
}
Adding a
router.refresh()
after push fixes it. @joulev from the discord NextJS server pointed me to this article which explains why this is required in more details.