Несколько меню в таблице, не в состоянии правильно переключаться - PullRequest
0 голосов
/ 28 марта 2020

песочница кода

Контекст

  • У меня есть таблица с 3 строками.
  • Каждая строка имеет кнопка для переключения меню под

Ошибка

При нажатии на ту же кнопку, невозможно переключить меню. В противном случае, кажется, что работают следующие требования

Требования

  • Одновременно открывается одно меню
  • Нажмите кнопку, под меню появится показать, изначально
  • Нажмите ту же кнопку, чтобы переключить скрытие / отображение меню.
  • Нажмите на само меню, оно должно оставаться открытым.
  • Нажмите на меню снаружи (включая родительская кнопка), меню должно закрыться.
  • Нажать другую кнопку, закрыть предыдущее меню, должно открыться текущее меню.

Код

Приложение. js

import React, { useState } from "react";
import Menu from "./Menu";

const getColor = index => {
  if (index === 0) return "blue";
  else if (index === 1) return "green";
  else if (index === 2) return "red";
};

function App() {
  // items
  const items = ["item0", "item1", "item2"];

  // state: show hide
  const [currIndex, setCurrIndex] = useState(-1);

  // close menu, set index -1
  const closeMenu = () => {
    setCurrIndex(-1);
  };

  // same, set not same
  const toggleMenu = index => {
    // toggle
    if (index !== currIndex) setCurrIndex(index);
    else setCurrIndex(-1);
  };

  return (
    <div className="App">
      {items.map((item, index) => {
        // loop items
        return (
          <div
            key={index}
            style={{
              backgroundColor: getColor(index),
              height: "100px",
              width: "200px"
            }}
          >
            <Menu
              index={index}
              isShowMenu={index === currIndex}
              closeMenu={closeMenu}
              toggleMenu={toggleMenu}
            />
          </div>
        );
      })}
    </div>
  );
}

export default App;

меню. js

import React, { useRef, useEffect, useState } from "react";
import { eventPath } from "./eventPath.js";

function Menu({ index, isShowMenu, closeMenu, toggleMenu }) {
  // menu ref
  const menuRef = useRef(null);

  // if really click
  const handleClick = event => {
    //test
    console.log("handleClick");

    if (menuRef === null || menuRef.current === null) {
      console.log("menuRef null out");
      return;
    }

    // find click target
    const target = event.target.shadowRoot ? eventPath(event)[0] : event.target;

    // click target not inside menu, close menu
    if (!menuRef.current.contains(target)) {
      closeMenu();
    }
  };

  // listen any time
  useEffect(() => {
    //test
    console.log("add listener");
    document.addEventListener("mousedown", handleClick);
    document.addEventListener("touchstart", handleClick);

    return () => {
      // don't listen mouse down
      document.removeEventListener("mousedown", handleClick);
      document.removeEventListener("touchend", handleClick);
    };
  });

  return (
    <>
      <button
        // button click
        onClick={event => {
          // toggle menu
          toggleMenu(index);
        }}
      >
        click to toggle
      </button>
      {isShowMenu && (
        <div ref={menuRef} style={{ backgroundColor: "white" }}>
          menu
        </div>
      )}
    </>
  );
}

export default Menu;

eventPath. js

// get parents
export const getParents = (node, memo) => {
  memo = memo || [];
  const parentNode = node.parentNode;

  if (!parentNode) {
    return memo;
  } else {
    return getParents(parentNode, memo.concat([parentNode]));
  }
};

// event path, event
export const eventPath = event => {
  let path = (event.composedPath && event.composedPath()) || event.path;
  const target = event.target;

  if (path != null) {
    path = path.indexOf(window) < 0 ? path.concat([window]) : path;
    return path;
  }

  if (target === window) {
    return [window];
  }

  return [target].concat(getParents(target)).concat([window]);
};

export default eventPath;

1 Ответ

0 голосов
/ 28 марта 2020

Приложение. js

import React, { useState } from "react";
import Menu from "./Menu";

const getColor = index => {
  if (index === 0) return "blue";
  else if (index === 1) return "green";
  else if (index === 2) return "red";
};

function App() {
  // items
  const items = ["item0", "item1", "item2"];

  // state: show hide
  const [currIndex, setCurrIndex] = useState(-1);

  // close menu, set index -1
  const closeMenu = () => {
    setCurrIndex(-1);
  };

  // same, set not same
  const toggleMenu = index => {
    // toggle
    if (index !== currIndex) setCurrIndex(index);
    else setCurrIndex(-1);
  };

  const shouldShowMenu = index => {
    return index === currIndex;
  };

  return (
    <div className="App">
      {items.map((item, index) => {
        // loop items
        return (
          <div
            key={index}
            style={{
              backgroundColor: getColor(index),
              height: "100px",
              width: "200px"
            }}
          >
            <Menu
              index={index}
              isShowMenu={shouldShowMenu(index)}
              closeMenu={closeMenu}
              toggleMenu={toggleMenu}
            />
          </div>
        );
      })}
    </div>
  );
}

export default App;

Меню. js

import React, { useRef, useEffect, useState, useCallback } from "react";
import { eventPath } from "./eventPath.js";

function Menu({ index, isShowMenu, closeMenu, toggleMenu }) {
  // menu ref
  const menuRef = useRef(null);

  // this run, then button click will run
  const handleClick = event => {
    console.log("-- 3 --", "handleClick index: ", index);

    if (menuRef.current === null) {
      // * the menu won't appear, so ref is null, until click the button
      console.log("-- 3.1 --", "menuRef is null, no more checking in or out");
      return;
    }

    const target = event.target.shadowRoot ? eventPath(event)[0] : event.target;
    // yes click outside
    if (!menuRef.current.contains(target)) {
      //test
      console.log("target", target);
      const ownButtonId = "button_" + index;
      if (ownButtonId === target.id) {
        // click own ... button, skip this
      } else {
        // or click outside or other button, close this
        closeMenu();
      }

      /*
      if (isButtonClickRef.current) {
        console.log("-- 3.2 --", "click ... button, no close menu");
      } else {
        // * or current click outside area, this should close
        console.log("-- 3.4 --", "close outside, close menu");
        closeMenu();
      }
      */
    } else {
      // click inside
      console.log("-- 3.5 --", "click inside download button");
    }
  };

  // listener added at page load
  useEffect(() => {
    //test
    console.log("-- 2 --", "add listener");
    document.addEventListener("mousedown", handleClick);
    document.addEventListener("touchstart", handleClick);

    return () => {
      //test
      console.log("-- 2.1 --", "unmount, remove listener");
      document.removeEventListener("mousedown", handleClick);
      document.removeEventListener("touchend", handleClick);
    };
  });

  return (
    <div>
      <button
        id={"button_" + index}
        onClick={event => {
          console.log("-- 1 -- button click");
          toggleMenu(index);
        }}
      >
        click to toggle
      </button>
      {isShowMenu && (
        <div ref={menuRef} style={{ backgroundColor: "white" }}>
          menu
        </div>
      )}
    </div>
  );
}

export default Menu;
...