Using hooks to set menu item className in react

Viewed 317

I am trying to create a simple menu using react and I can't for the life of me work this out.

Please see my attached code, when the user hovers or clicks on the NavButton a classname is added for either clicked or active depending on if hovered over or clicked.

This works for the first one I code but I need to do the same for the other items. As soon as I add the code for all my NavButton's, when the user clicks or hovers over any one, they all change where as only the one you click or hover should change.

I spent all night on this and thought a sleep would do me good but still no further forward.

I'm thinking I somehow need to work out out if something is true && hover then add the className.

Thoughts??

//********************************************
// IMPORTS
//********************************************
import Head from "next/head";
import Link from "next/link";
import { useState } from "react";
import styled from "styled-components";
import Theme from "../Theme/Theme";
import NavbarOne from "./Navbars/NavbarOne";
import NavbarTwo from "./Navbars/NavbarTwo";
const classNames = require("classnames");
//********************************************
// COMPONENT
//********************************************

const Layout = ({ children }) => {
  const [isActive, setIsActive] = useState(false);
  const [isClicked, setIsClicked] = useState(false);

  return (
    <Wrapper>
      <Head>
        <link
          rel="stylesheet"
          href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"
        />
      </Head>
      <Dashboard>
        <Nav>
          <NavButton
            onMouseEnter={() => setIsActive(!isActive)}
            onMouseLeave={() => setIsActive(!isActive)}
            onClick={() => setIsClicked(!isClicked)}
            className={classNames({
              ["active"]: isActive,
              ["clicked"]: isClicked,
            })}
          >
            M
          </NavButton>
          <NavButton
            className={classNames({
              ["active"]: isActive,
              ["clicked"]: isClicked,
            })}
          >
            1
          </NavButton>
          <NavButton
            className={classNames({
              ["active"]: isActive,
              ["clicked"]: isClicked,
            })}
          >
            2
          </NavButton>
          <NavButton
            className={classNames({
              ["active"]: isActive,
              ["clicked"]: isClicked,
            })}
          >
            3
          </NavButton>
          <NavButton
            className={classNames({
              ["active"]: isActive,
              ["clicked"]: isClicked,
            })}
          >
            4
          </NavButton>
        </Nav>
      </Dashboard>
      {/* <NavbarOne /> */}
      {/* <NavbarTwo /> */}
      {/* <Wrapper>{children}</Wrapper> */}
    </Wrapper>
  );
};

//********************************************
// STYLES
//********************************************
const Wrapper = styled.div`
  display: flex;
  flex-direction: row; // row | row-reverse | column | column-reverse
  flex-wrap: nowrap; // nowrap | wrap | wrap-reverse
  flex: 1;
`;
const Dashboard = styled.div`
  display: flex;
  flex: 1;
  height: 100vh;
  background: red;
`;
//********************************************
// NAV
//********************************************
const Nav = styled.div`
  /* &:nth-child(2) {
    transition-duration: 190ms;
    transform: translate3d(0, 110px, 0);
  }
  &:nth-child(3) {
    transition-duration: 190ms;
    transform: translate3d(0, 110px, 0);
  }
  &:nth-child(4) {
    transition-duration: 290ms;
    transform: translate3d(0, 220px, 0);
  }
  &:nth-child(5) {
    transition-duration: 390ms;
    transform: translate3d(0, 330px, 0);
  }
  &:nth-child(6) {
    transition-duration: 490ms;
    transform: translate3d(0, 440px, 0);
  } */
`;
const NavButton = styled.div`
  //Border
  border-radius: 100%;
  //Colour
  color: white;
  background: #3498db;
  //Positioning
  position: absolute;
  top: 25px;
  left: 25px;
  //Sizing
  width: 60px;
  height: 60px;
  //Text
  text-align: center;
  line-height: 60px;
  //Transitions
  transition: transform ease-out 200ms;
  &:nth-child(1) {
    //Positioning
    position: absolute;
    top: 25px;
    left: 25px;
    z-index: 2;
  }

  &.active {
    transform: scale(1.2, 1.2) translate3d(0, 0, 0);
  }

  &.clicked {
    transition-duration: 400ms;

    &:nth-child(2) {
      //Positioning
      transition-duration: 50ms;
      transform: translate3d(0, 85px, 0);
    }
    &:nth-child(3) {
      transition-duration: 100ms;
      transform: translate3d(0, 170px, 0);
    }
    &:nth-child(4) {
      transition-duration: 150ms;
      transform: translate3d(0, 255px, 0);
    }
    &:nth-child(5) {
      transition-duration: 200ms;
      transform: translate3d(0, 340px, 0);
    }
  }
`;

//********************************************
// EXPORT
//********************************************
export default Layout;
2 Answers

You only have a single value for isActive, which you are passing to all of your NavButtons. That's why changing one is changing all of them: they're sharing the value. You could solve this by making isActive an array, but it would be better to let the navButton components handle their own state.

It is also worth mentioning that CSS has pseudo-selectors for these cases, called :hover and :active. It would probably be easier to use these for styling.

There are also React handlers for native JavaScript events that may help: onFocus and onClick. These are how you would trigger code to run on these events.

As mentioned in other answer, you can use CSS Pseudo-Selectors for hover & active styling.

And, as mentioned in comment, you can easily set active, hovered class for your buttons by maintaining active / hovered button id or name.

Here is a simple example to do the same using button names and storing the clicked and hovered one in states:

const buttons = ["A", "B", "C", "D", "E"];

const Layout = () => {
  const [active, setActive] = useState("");
  const [hovered, setHovered] = useState("");

  return (
    <>
      {buttons.map((name) => (
        <button
          key={name}
          onMouseOver={() => setHovered(name)}
          onClick={() => setActive(name)}
          className={classNames({
            active: active === name,
            hovered: hovered === name,
          })}
        >
          {name}
        </button>
      ))}
    </>
  );
};

CSS:

.active {
  background-color: blue;
}

.hovered {
  background-color: yellow;
}

Related