React state and simplified logic.

  1. I updated the navbar.

"use client";

import React from "react";
import { AiOutlineMenu, AiOutlineClose } from "react-icons/ai";

interface NavBarProps {
  isSidebarOpen: boolean; // Track sidebar state
  toggleSidebar: () => void; // Function to toggle sidebar
}

const NavBar: React.FC<NavBarProps> = ({ isSidebarOpen, toggleSidebar }) => {
  const toggleDropdown = () => {
    setDropdownVisible(!dropdownVisible);
  };

  return (
    <nav className="bg-white backdrop-blur-md z-50 fixed top-0 left-0 right-0 mx-auto rounded-lg shadow-lg w-full p-4 flex justify-between items-center">
      <div className="flex items-center space-x-4">
        <div className="md:hidden">
          <button onClick={toggleSidebar}>
            {isSidebarOpen ? (
              <AiOutlineClose className="text-3xl text-cyan-700" />
            ) : (
              <AiOutlineMenu className="text-3xl text-cyan-700" />
            )}
          </button>
        </div>
      </div>

    export default NavBar;
  1. I updated the sidebar and passed the isOpen state as a prop.

"use client";

import { FC } from "react";

interface SidebarProps {
  isOpen: boolean;
}

const Sidebar: FC<SidebarProps> = ({ isOpen }) => {
  return (
    <aside
      className={`${
        styles.sidebar
      } fixed top-[64px] left-0 h-[calc(100vh-64px)] w-64 bg-[#f0f0f0] text-black text-md transform ${
        isOpen ? "translate-x-0" : "-translate-x-full"
      } md:translate-x-0 transition-transform duration-300 ease-in-out z-40 overflow-y-auto`}
    >
      {/* Profile Section */}

      {/* Main Navigation */}

    </aside>
  );
};

export default Sidebar;
  1. Lastly, I updated the layout component.

    I passed the isSidebarOpen state and the toggleSidebar function from the Layout component to both NavBar and Sidebar.

"use client";

import { useState } from "react";
import Sidebar from "./Sidebar";
import NavBar from "./dashcomponents/Navbar";

export default function Layout({ children }: { children: React.ReactNode }) {
  const [isSidebarOpen, setIsSidebarOpen] = useState(false);

  const toggleSidebar = () => {
    setIsSidebarOpen((prev) => !prev);
  };

  return (
    <div className="h-screen">
      {/* Sidebar */}
      <Sidebar isOpen={isSidebarOpen} />

      <div className="flex flex-col">
        {/* NavBar */}
        <NavBar isSidebarOpen={isSidebarOpen} toggleSidebar={toggleSidebar} />

        {/* Main Content */}
        <main className="my-[6rem] md:ml-[18rem] w-[90%] mx-auto md:max-w-5xl overflow-x-hidden overflow-y-auto">
          {children}
        </main>
      </div>
    </div>
  );
}

Logic Explanation:

  • NavBar: The hamburger menu (or close icon) is conditionally rendered using a ternary operator based on the state of isSidebarOpen. If isSidebarOpen is true, it shows the close icon (AiOutlineClose); otherwise, it shows the hamburger menu (AiOutlineMenu).

    When the button is clicked, it triggers the toggleSidebar function, which toggles the value of isSidebarOpen between true and false. This causes a re-render in the NavBar to switch between the hamburger and close icons, and also controls the visibility of the sidebar.

  • Sidebar: The sidebar is controlled by the isOpen prop, which determines whether it is shown or hidden. The sidebar slides in and out by applying CSS classes using the Tailwind utility classes like translate-x-0 (to show) and -translate-x-full (to hide). These classes are toggled based on the isOpen prop.

  • Layout Component: This component manages the state of the sidebar (isSidebarOpen) and passes it along with the toggleSidebar function as props to both the NavBar and the Sidebar. This allows the sidebar to be opened and closed when the user clicks the button in the NavBar.

Expected Behavior:

  • On mobile devices or smaller screens, the NavBar will show the hamburger menu when the sidebar is hidden and the close icon when the sidebar is open.

  • When the user clicks the hamburger menu, the sidebar will slide into view, and the menu icon will change to the close icon.

  • Clicking the close icon will slide the sidebar out of view and change the icon back to the hamburger menu.

The function toggleSidebar is used to toggle the visibility of the sidebar by changing the isSidebarOpen state.

Here's a detailed breakdown of what is happening:

const toggleSidebar = () => {
  setIsSidebarOpen((prev) => !prev);
};

Key Concepts:

  1. State Update Function (setIsSidebarOpen):

    • setIsSidebarOpen is a state update function provided by the useState hook.

    • The purpose of this function is to update the value of the isSidebarOpen state, which determines whether the sidebar is open or closed.

  2. Previous State (prev):

    • In this case, the setIsSidebarOpen function is using a callback form. When you pass a function into the state update function, React provides the previous state value (prev) as the argument to that function.

    • This means that instead of directly setting the state to true or false, you're getting the current state value (prev) and then determining what the next value should be.

  3. Toggle Logic (!prev):

    • The !prev expression is a logical NOT operator, which flips the value of prev.

    • If prev (the previous state) is true, !prev will be false. If prev is false, !prev will be true.

    • In other words, it inverts the current state. If the sidebar is currently open (i.e., isSidebarOpen is true), it will set it to false (closing the sidebar), and if the sidebar is closed (isSidebarOpen is false), it will set it to true (opening the sidebar).

  4. What Happens in the Application:

    • When toggleSidebar is called (e.g., when the user clicks the hamburger menu or close icon), it checks the current state of the sidebar (isSidebarOpen).

      • If the sidebar is open (isSidebarOpen === true), calling toggleSidebar will close it (set isSidebarOpen to false).

      • If the sidebar is closed (isSidebarOpen === false), calling toggleSidebar will open it (set isSidebarOpen to true).

    • This allows the sidebar to be toggled on and off whenever the function is invoked.

Why Use the Callback Form?

Using the callback form of setIsSidebarOpen((prev) => !prev) ensures that you're always working with the most up-to-date state value. This is particularly important if multiple components or functions might change the state asynchronously. In contrast, using setIsSidebarOpen(!isSidebarOpen) directly could cause issues if React batches multiple state updates.

Example:

Let's say the isSidebarOpen state is currently false (sidebar is closed).

  1. When you click the menu button, toggleSidebar is called.

  2. Inside toggleSidebar, the prev value will be false, because that was the previous state.

  3. The !prev expression flips false to true, so isSidebarOpen is updated to true (sidebar is now open).

  4. The component re-renders, showing the sidebar.

If you click the button again, the process repeats but this time the previous state (prev) will be true, and the sidebar will be closed.

This approach makes the function concise and ensures the correct toggling behavior for the sidebar.

Object destructuring ({ isOpen })

In React, ({ isOpen }) is a form of object destructuring used to access the isOpen property from an object directly.

What does ({ isOpen }) mean?

When you see ({ isOpen }) in a function parameter, it means that an object is being passed to the function, and you want to extract the isOpen property from that object right away without needing to access it using the dot notation like props.isOpen. Essentially, it's a shortcut.

For example:

const MyComponent = ({ isOpen }) => {
  return <div>{isOpen ? 'Sidebar is open' : 'Sidebar is closed'}</div>;
};

Breakdown:

  • isOpen is a boolean value (or any value) that could be passed as a prop.

  • Instead of doing this:

      const MyComponent = (props) => {
        return <div>{props.isOpen ? 'Sidebar is open' : 'Sidebar is closed'}</div>;
      };
    

    You can use destructuring directly in the function argument to access isOpen:

      const MyComponent = ({ isOpen }) => {
        return <div>{isOpen ? 'Sidebar is open' : 'Sidebar is closed'}</div>;
      };
    

Example Usage:

In the context of your sidebar, isOpen might be a prop passed down to a child component or a part of your component’s state that controls the visibility of the sidebar.

const Sidebar = ({ isOpen }) => {
  return (
    <aside className={isOpen ? "sidebar-open" : "sidebar-closed"}>
      {/* Sidebar content */}
    </aside>
  );
};

Explanation:

  1. Object Destructuring: Instead of passing and then accessing the whole props object (e.g., props.isOpen), you are only grabbing the isOpen property directly. It makes the code more concise.

  2. isOpen as a Prop: If isOpen is passed to the Sidebar component, this means the parent component is controlling whether the sidebar is open or closed, and the child component is rendering based on this value.

When Is This Used?

  • This is commonly used in functional components when you need to extract specific properties (props or state) passed into the component.

  • It makes the code cleaner and easier to read, especially when dealing with multiple properties, like this:

      const MyComponent = ({ isOpen, toggleSidebar, title }) => {
        return (
          <div>
            <h1>{title}</h1>
            <button onClick={toggleSidebar}>{isOpen ? 'Close Sidebar' : 'Open Sidebar'}</button>
          </div>
        );
      };
    

In this case, isOpen, toggleSidebar, and title are destructured right in the function signature, so you can directly access them without needing to write props.isOpen, props.toggleSidebar, or props.title each time.

0
Subscribe to my newsletter

Read articles from Hillary Nyakundi directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Hillary Nyakundi
Hillary Nyakundi

I am a software engineer who is fascinated with building exclusive technology.