#NextJS 13 app router doesn't refresh data on update with router.push

6 messages · Page 1 of 1 (latest)

idle kite
#

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.

shell pastureBOT
#

🔎 This post has been indexed in our web forum and will be seen by search engines so other users can find it outside Discord

🕵️ Your user profile is private by default and won't be visible to users outside Discord, if you want to be visible in the web forum you can add the "Public Profile" role in id:customize

✅ You can mark a message as the answer for your post with Right click -> Apps -> Mark Solution
(if you don't see the option, try refreshing Discord with Ctrl + R)

idle kite
#

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";
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>
  );
}
strong yoke