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.