Why does updating a state in a client component cause my entire page to rerender with Next 14 App directory?

17 Views Asked by At

I'm working on a Next.js application that uses a server component to render a client component.

However, whenever I update the state in the client component, it causes my entire page to rerender, leading to flickering and an undesirable user experience. I'm particularly puzzled because I expected only the client component to rerender, not the entire page.

Any help is highly appriciated!

Server Layout File:

import React, { Suspense } from "react";
import { getUserProfile } from "../../helpers/supabase";
import { redirect } from "next/navigation";
import DashboardLayout from "../../components/dashboard/dashboardLayout";

export default async function Component({ children }) {
  const user = await getUserProfile();

  if (!user) {
    redirect("/login");
  } else {
    return (
      <Suspense fallback="Loading...">
        <DashboardLayout user={user}>{children}</DashboardLayout>
      </Suspense>
    );
  }
}

Client Component:

"use client";

import React, { useEffect, useState } from "react";
import { Avatar, Button, ScrollShadow, Spacer } from "@nextui-org/react";
import { Icon } from "@iconify/react";
import Sidebar from "./sidebar";
import { brandItems } from "./sidebar-items";
import { Consica } from "./Consica";
import { Tables } from "../../types/supabase";
import { createClient } from "../../utils/supabase/client";
import { cn } from "../../utils/cn";
import { usePathname, useRouter } from "next/navigation";

interface DashboardLayoutProps {
  user: Tables<"profiles">;
  children: React.ReactNode;
}

export default async function DashboardLayout({
  user,
  children,
}: DashboardLayoutProps) {
  const [isHidden, setIsHidden] = useState<boolean>(false);
  const [counter, setcounter] = useState(0);
  const pathname = usePathname();
  const sb = createClient();

  const handleSignOut = async () => {
    const { error } = await sb.auth.signOut();
    if (error) {
      console.error(error);
    }
  };

  return (
     <div className="flex h-dvh w-full">
      <div
        className={cn(
          "relative hidden h-full w-0 max-w-[288px] flex-1 flex-col bg-primary  p-6 transition-[transform,opacity,margin] duration-250 ease-in-out lg:flex lg:w-72",
          {
            "-ml-72 -translate-x-72": isHidden,
          }
        )}
      >
        <div className="flex items-center gap-2 px-2">
          <div className="flex h-20 w-20 items-center justify-center rounded-full border-small border-primary-foreground/20">
            <Consica />
          </div>
        </div>

        <p>Counter: {counter} </p>

        <Button onClick={() => setcounter(counter + 1)}>+1</Button>

        <Spacer y={8} />

        <div className="flex flex-col gap-4">
          <div className="flex items-center gap-3 px-2">
            <Avatar
              size="sm"
              src="https://i.pravatar.cc/150?u=a04258114e29028708c"
            />
            <div className="flex flex-col">
              <p className="text-small text-primary-foreground">
                {user.full_name}
              </p>
              <p className="text-tiny text-primary-foreground/60">Udvikler</p>
            </div>
          </div>
        </div>

        <ScrollShadow className="-mr-6 h-full max-h-full py-2 pr-6">
          <Sidebar
            selectedKeys={[pathname.split("/")[2] || "home"]}
            defaultSelectedKey={pathname.split("/")[2] || "home"}
            iconClassName="text-primary-foreground/60 group-data-[selected=true]:text-primary-foreground"
            itemClasses={{
              base: "data-[selected=true]:bg-primary-400 dark:data-[selected=true]:bg-primary-300 data-[hover=true]:bg-primary-300/20 dark:data-[hover=true]:bg-primary-300/40",
              title:
                "text-primary-foreground/60 group-data-[selected=true]:text-primary-foreground",
            }}
            items={brandItems}
            sectionClasses={{
              heading: "text-primary-foreground/80",
            }}
            variant="flat"
          />
        </ScrollShadow>

        <Spacer y={8} />

        <div className="mt-auto flex flex-col">
          <Button
            fullWidth
            className="justify-start text-primary-foreground/60 data-[hover=true]:text-white"
            startContent={
              <Icon
                className="text-primary-foreground/70"
                icon="solar:info-circle-line-duotone"
                width={24}
              />
            }
            variant="light"
          >
            Help & Information
          </Button>
          <Button
            className="justify-start text-primary-foreground/60 data-[hover=true]:text-white"
            startContent={
              <Icon
                className="rotate-180 text-primary-foreground/70"
                icon="solar:minus-circle-line-duotone"
                width={24}
              />
            }
            variant="light"
          >
            Log Out
          </Button>
        </div>
      </div>

      <div
        className={cn("flex h-full w-full max-w-full flex-col gap-4 p-4", {
          "lg:max-w-[calc(100%-288px)]": !isHidden,
        })}
      >
        <header className="flex flex-wrap items-center justify-between gap-2 rounded-medium border-small border-divider p-4">
          <div className="flex items-center gap-3">
            <Button
              isIconOnly
              size="sm"
              variant="light"
              onPress={() => setIsHidden(!isHidden)}
            >
              <Icon
                className="text-default-500"
                height={24}
                icon="solar:sidebar-minimalistic-outline"
                width={24}
              />
            </Button>
            <h2 className="text-medium font-medium text-default-700">
              {pathname.charAt(0).toUpperCase() + pathname.slice(1)}
            </h2>
          </div>
          header
        </header>
        <main className="h-full max-h-[calc(100%_-_98px)] w-full overflow-visible">
          <div className="flex h-full w-full flex-col gap-4 rounded-medium border-small border-divider p-6">
            {children}
          </div>
        </main>
      </div>
    </div>
  );
}

To use a server-component as layout, then populate the client component with the data. But i need to be able to make the menu collapsable without the page rerenders to do it, so it can have a nice and smooth animation.

0

There are 0 best solutions below