Как я могу сделать так, чтобы активный элемент боковой панели отражал текущий URL-адрес в Next.js?

Я работаю над проектом Next.js и пытаюсь убедиться, что активный элемент боковой панели правильно отражает текущий URL-адрес. Я попытался использовать крючок useRouter в сочетании с useEffect внутри функционального компонента. Однако я столкнулся с проблемой, из-за которой элемент боковой панели не обновляется должным образом. Кроме того, я получаю сообщение об ошибке NextRouter was not mounted.

Вот фрагмент кода, с которым я работаю:

"use client";
import React, { useState, useEffect } from "react";
import Link from "next/link";
import "@/app/globals.css";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import {
  Package,
  SquareArrowUpRight,
  HandCoins,
  Users,
  ScanEye,
  LayoutDashboard,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";

export default function Sidebar() {
  const [activeItem, setActiveItem] = useState(0);
  const handleItemClick = (index) => setActiveItem(index);
  const allItemClass =
    "flex items-center gap-3 rounded-lg px-3 py-2 transition-all";
  const activeItemClass =
    "bg-primary text-white dark:bg-primary dark:text-black";
  const inactiveItemClass = "text-muted-foreground hover:text-primary";
  const { setTheme } = useTheme();

  // Ensure theme is set on the client side
  useEffect(() => {
    setTheme("system");
  }, [setTheme]);

  return (
    <div className = "flex min-h-screen w-1/4">
      <div className = "hidden border-r bg-muted/40 md:block">
        <div className = "flex h-full max-h-screen flex-col gap-2">
          <div className = "flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6">
            <Link
              href = "/dashboard"
              className = "flex items-center gap-2 font-semibold"
            >
              <ScanEye className = "h-6 w-6" />
              <span className = "">Reebews</span>
            </Link>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button
                  variant = "outline"
                  size = "icon"
                  className = "ml-auto w-8 h-8"
                >
                  <Sun className = "h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
                  <Moon className = "absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
                  <span className = "sr-only">Toggle theme</span>
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align = "end">
                <DropdownMenuItem onClick = {() => setTheme("light")}>
                  Light
                </DropdownMenuItem>
                <DropdownMenuItem onClick = {() => setTheme("dark")}>
                  Dark
                </DropdownMenuItem>
                <DropdownMenuItem onClick = {() => setTheme("system")}>
                  System
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>

          <div className = "flex-1">
            <nav className = "grid items-start px-2 text-sm font-medium lg:px-4">
              <Link
                href = "/dashboard"
                className = {`${allItemClass} ${
                  activeItem === 0 ? activeItemClass : inactiveItemClass
                }`}
                onClick = {() => handleItemClick(0)}
              >
                <LayoutDashboard className = "h-4 w-4" />
                Dashboard
              </Link>
              <Link
                href = "/campaigns"
                className = {`${allItemClass} ${
                  activeItem === 1 ? activeItemClass : inactiveItemClass
                }`}
                onClick = {() => handleItemClick(1)}
              >
                <SquareArrowUpRight className = "h-4 w-4" />
                Campaign Manager
              </Link>
              <Link
                href = "/products"
                className = {`${allItemClass} ${
                  activeItem === 2 ? activeItemClass : inactiveItemClass
                }`}
                onClick = {() => handleItemClick(2)}
              >
                <Package className = "h-4 w-4" />
                Products{" "}
              </Link>
              <Link
                href = "/rewards-center"
                className = {`${allItemClass} ${
                  activeItem === 3 ? activeItemClass : inactiveItemClass
                }`}
                onClick = {() => handleItemClick(3)}
              >
                <HandCoins className = "h-4 w-4" />
                Rewards Center
              </Link>
              <Link
                href = "/customer-list"
                className = {`${allItemClass} ${
                  activeItem === 4 ? activeItemClass : inactiveItemClass
                }`}
                onClick = {() => handleItemClick(4)}
              >
                <Users className = "h-4 w-4" />
                Customer List
              </Link>
            </nav>
          </div>
          <div className = "mt-auto p-4">
            <Card x-chunk = "dashboard-02-chunk-0">
              <CardHeader className = "p-2 pt-0 md:p-4">
                <CardTitle>Upgrade to Pro</CardTitle>
                <CardDescription>
                  Unlock all features and get unlimited access to our support
                  team.
                </CardDescription>
              </CardHeader>
              <CardContent className = "p-2 pt-0 md:p-4 md:pt-0">
                <Button size = "sm" className = "w-full">
                  Upgrade
                </Button>
              </CardContent>
            </Card>
          </div>
        </div>
      </div>
    </div>
  );
}

Как я могу правильно синхронизировать активный элемент боковой панели с текущим URL-адресом, чтобы обеспечить правильное выделение активного элемента?

🤔 А знаете ли вы, что...
JavaScript позволяет создавать мобильные приложения для iOS и Android с использованием фреймворков, таких как React Native и NativeScript.


50
1

Ответ:

Решено

Вы можете добиться этого, используя крючок usePathname NextJS. Вы можете сопоставить свой активный URL-адрес с меню навигации и определить по нему активное меню. Я скопировал ваш код со своей стороны, а также провел огромный рефакторинг вашего кода, поскольку в вашем коде слишком много дубликатов. Кроме того, чтобы сохранить ваш код СУХИМ, я провел следующий рефакторинг:

//@/components/layout/NavItem.js

"use client";

import Link from "next/link";
import { usePathname } from "next/navigation";


export default function NavItem({ label, Icon, path, onClick }) {
  const pathname = usePathname();

  return (
    <Link
      href = {path}
      className = {cn(
        "flex items-center gap-3 rounded-lg px-3 py-2 transition-all text-muted-foreground hover:text-primary",
        {
          "bg-primary text-white dark:bg-primary dark:text-black":
            pathname === path,
        }
      )}
      onClick = {onClick}
    >
      <Icon className = "h-4 w-4" />
      {label}
    </Link>
  );
}

Я выделил ваше индивидуальное навигационное меню в отдельный компонент и применил к нему активные и неактивные классы.

"use client";
import React, {  useEffect } from "react";
import Link from "next/link";
import "@/app/globals.css";
import { Moon, Sun } from "lucide-react";
import { useTheme } from "next-themes";
import {
  Package,
  SquareArrowUpRight,
  HandCoins,
  Users,
  ScanEye,
  LayoutDashboard,
} from "lucide-react";
import { Button } from "@/components/ui/button";
import {
  Card,
  CardContent,
  CardDescription,
  CardHeader,
  CardTitle,
} from "@/components/ui/card";
import {
  DropdownMenu,
  DropdownMenuContent,
  DropdownMenuItem,
  DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import NavItem from "./nav-item";

const sideMenus = [
  { label: "Dashboard", path: "/dashboard", Icon: LayoutDashboard },
  { label: "Campaign Manager", path: "/campaigns", Icon: SquareArrowUpRight },
  { label: "Products", path: "/products", Icon: Package },
  { label: "Rewards Center", path: "/rewards-center", Icon: HandCoins },
  { label: "Customer List", path: "/customer-list", Icon: Users },
];

export default function Sidebar() {
  const { setTheme } = useTheme();

  // Ensure theme is set on the client side
  useEffect(() => {
    setTheme("system");
  }, [setTheme]);

  return (
    <div className = "flex min-h-screen w-1/4">
      <div className = "hidden border-r bg-muted/40 md:block">
        <div className = "flex h-full max-h-screen flex-col gap-2">
          <div className = "flex h-14 items-center border-b px-4 lg:h-[60px] lg:px-6">
            <Link
              href = "/dashboard"
              className = "flex items-center gap-2 font-semibold"
            >
              <ScanEye className = "h-6 w-6" />
              <span className = "">Reebews</span>
            </Link>
            <DropdownMenu>
              <DropdownMenuTrigger asChild>
                <Button
                  variant = "outline"
                  size = "icon"
                  className = "ml-auto w-8 h-8"
                >
                  <Sun className = "h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
                  <Moon className = "absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
                  <span className = "sr-only">Toggle theme</span>
                </Button>
              </DropdownMenuTrigger>
              <DropdownMenuContent align = "end">
                <DropdownMenuItem onClick = {() => setTheme("light")}>
                  Light
                </DropdownMenuItem>
                <DropdownMenuItem onClick = {() => setTheme("dark")}>
                  Dark
                </DropdownMenuItem>
                <DropdownMenuItem onClick = {() => setTheme("system")}>
                  System
                </DropdownMenuItem>
              </DropdownMenuContent>
            </DropdownMenu>
          </div>

          <div className = "flex-1">
            <nav className = "grid items-start px-2 text-sm font-medium lg:px-4">
              {sideMenus.map((menu) => (
                <NavItem key = {menu.label} {...menu} />
              ))}
            </nav>
          </div>
          <div className = "mt-auto p-4">
            <Card x-chunk = "dashboard-02-chunk-0">
              <CardHeader className = "p-2 pt-0 md:p-4">
                <CardTitle>Upgrade to Pro</CardTitle>
                <CardDescription>
                  Unlock all features and get unlimited access to our support
                  team.
                </CardDescription>
              </CardHeader>
              <CardContent className = "p-2 pt-0 md:p-4 md:pt-0">
                <Button size = "sm" className = "w-full">
                  Upgrade
                </Button>
              </CardContent>
            </Card>
          </div>
        </div>
      </div>
    </div>
  );
}

Кроме того, я вычислил ваш набор навигационных данных в массив с именем sideMenus, и отдельный элемент массива сопоставляется с компонентом NavItem. Я надеюсь, что это сработает для вас.