简体   繁体   中英

React: Proper state mutation with setState hook

I am new to React, and basically i want to build a megamenu from scratch. To do this i have event listeners for mouseenter and mouseleave events as well as the useState hook from React.

Component implementation so far

import { useState, useEffect } from "react";
import { ArrowSmRightIcon } from "@heroicons/react/solid";

export default function Navbar({ props }) {
  const { links } = props;

  const [className, setclassName] = useState("bg-white/15 backdrop-blur-lg");
  const [panelOpen, setpanelOpen] = useState(false);
  const [usingPanel, setusingPanel] = useState(false);

  const scrollHandler = (event) => {
    if (window.scrollY < 30) {
      setclassName("bg-white/15 backdrop-blur-lg");
    } else {
      setclassName("bg-white shadow-md");
    }
  };

  useEffect(() => {
    window.addEventListener("scroll", scrollHandler);
  }, []);

  const linkHoverHandler = (e) => {
    setpanelOpen(true);
  };

  const linkExitHandler = (e) => {
    setTimeout(() => {
      if (!usingPanel) {
        setpanelOpen(false);
      }
    }, 500);
  };

  const panelHoverHandler = (e) => {
    setusingPanel(true);
  };

  const panelExitHandler = (e) => {
    setusingPanel(false);
    setpanelOpen(false);
  };

  return (
    <div className={`w-full  ${className} transition-all sticky top-0`}>
      <div className="container mx-auto relative">
        <div className="flex items-center p-6 gap-6">
          <img
            className="h-10 flex-none"
            src="thumbnail_image001.png"
            alt="Digital Unity Logo"
          />

          <ul className="flex flex-1">
            {links.map((link, index) => (
              <li
                key={index}
                className="ml-5 hover:cursor-pointer"
                onMouseEnter={linkHoverHandler}
                onMouseLeave={linkExitHandler}
              >
                {link.label}
              </li>
            ))}
          </ul>
        </div>
        {panelOpen && (
          <div
            onMouseEnter={panelHoverHandler}
            onMouseLeave={panelExitHandler}
            className="absolute top-[6rem] left-1/2 bg-white w-full -translate-x-1/2 flex items-center rounded-lg"
          >
            <div className="w-1/2 p-5 rounded-l-lg bg-gray-100 border-solid border-r-2 border-r-gray-200">
              <div className="flex">
                <div className="flex-1">
                  <h5 className="text-lg text-sky-800 mb-5">Οι υπηρεσίες</h5>

                  <ul>
                    {["Η μια", "Η δυο", "Η τρια"].map((el, i) => (
                      <li
                        key={i}
                        className="mb-2 hover:text-sky-600 hover:cursor-pointer transition-all"
                      >
                        {el}
                      </li>
                    ))}
                  </ul>
                </div>
                <div className="flex-1">
                  <h5 className="text-lg text-sky-800 mb-5">Οι alles</h5>

                  <ul>
                    {["Η μια", "Η δυο", "Η τρια"].map((el, i) => (
                      <li
                        key={i}
                        className="mb-2 hover:text-sky-600 hover:cursor-pointer transition-all"
                      >
                        {el}
                      </li>
                    ))}
                  </ul>
                </div>
              </div>

              <div>
                <p className="hover:text-sky-600 hover:cursor-pointer my-2 flex items-center">
                  Ανακάλυψε τα έργα
                  <ArrowSmRightIcon className="h-5 w-5 text-sky-500" />
                </p>
                <p className="hover:text-sky-600 hover:cursor-pointer my-2 flex items-center">
                  Δες τα testemonials
                  <ArrowSmRightIcon className="h-5 w-5 text-sky-500" />
                </p>
              </div>
            </div>
            <div className="w-1/2 rounded-lg p-5">Diff Section</div>
          </div>
        )}
      </div>
    </div>
  );
}

Ignore all the greek in there

My problem

I get this very unexpted behaviour where setusingPanel doesn't mutate properly on the first time but only on the second. 在此处输入图像描述

Even when i log out the usingPanel variable it is only changed correctly the second time around. Am I using the hooks incorrectly?

It's because

const linkExitHandler = (e) => {
  setTimeout(() => {
    if (!usingPanel) {
      setpanelOpen(false);
    }
  }, 500);
};

creates a closure over the current value of usingPanel and does not contain the latest state.

You could make it a ref (see useRef() ). But I find it that construct with setpanelOpen but not if usingPanel too complicated; it soon gets unmanageable how these two interfere with each other.

You can use clearTimeout() and a ref to store the timeout identifier:

const [panelOpen, setpanelOpen] = useState(false);
const timeoutRef = useRef();

const showPanel = () => {
  clearTimeout(timeoutRef.current);
  setpanelOpen(true);
}

const hidePanel = () => {
  clearTimeout(timeoutRef.current);
  setTimeout(setpanelOpen, 500, true);
}

// and use 
onMouseEnter={showPanel}
onMouseLeave={hidePanel}
//on both links and panel

Another more abstract approach: Just use the state with the latest timestamp.
 const _takeLatest = (current, changes) => current && changes.ts < current.ts? current: changes; const [{ panelOpen = false }, dispatch] = useReducer(_takeLatest); const showPanel = () => { dispatch({ panelOpen: true, ts: precision.now() }); } const hidePanel = () => { setTimeout(dispatch, 500, { panelOpen: false, ts: precision.now() }); }

Another thing, of topic:

 useEffect(() => { const scrollHandler = (event) => { if (window.scrollY < 30) { setclassName("bg-white/15 backdrop-blur-lg"); } else { setclassName("bg-white shadow-md"); } }; window.addEventListener("scroll", scrollHandler); return () => { window.removeEventListener("scroll", scrollHandler); } }, []);
  1. Put scrollHandler inside of useEffect .

We don't need it outside and that way you avoid confusion as to which function from which render is used here; the same problem you currently have with usingPanel ;) 2. Clean up after yourself: remove the event handlers you add.

This seems like a pretty global component that will most likely exist as long as the page is loaded, but with smaller components you prevent them from being garbage collected, because that event handler still has a reference to them.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM