最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - React - how to click outside to close the tooltip - Stack Overflow

programmeradmin3浏览0评论

This is my current tooltip.

I am using react-power-tooltip

When I click the button, I can close the tooltip.

But I want to close the tooltip when I click outside the tooltip.

How am I supposed to do it?

App.js

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  return (
    <div className="App">
      <button
        className="post-section__body__list__item__right__menu-btn"
        onClick={() => {
          setShowTooltip((x) => !x);
        }}
        style={{ position: "relative" }}
      >
        <MoreHorizIcon />
        <TooltipList show={showTooltip} />
      </button>
    </div>
  );
}

TooltipList

import React from "react";
import Tooltip from "react-power-tooltip";

const options = [
  {
    id: "edit",
    label: "Edit"
  },
  {
    id: "view",
    label: "View"
  }
];

function Tooptip(props) {
  const { show } = props;
  return (
    <Tooltip
      show={show}
      position="top center"
      arrowAlign="end"
      textBoxWidth="180px"
      fontSize="0.875rem"
      fontWeight="400"
      padding="0.5rem 1rem"
    >
      {options.map((option) => {
        return (
          <div
            className="tooltop__option d-flex align-items-center w-100"
            key={option.id}
          >
            {option.icon}
            <span style={{ fontSize: "1rem" }}>{option.label}</span>
          </div>
        );
      })}
    </Tooltip>
  );
}

export default Tooptip;

CodeSandbox:
=/src/App.js

Update 1:
I update the code based on the answer.
It can now click outside to close, but if I click the button to close the tooltip, it's not working.

App.js

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  return (
    <div className="App">
      <button
        className="post-section__body__list__item__right__menu-btn"
        onClick={() => {
          setShowTooltip((x) => !x);
        }}
        style={{ position: "relative" }}
      >
        <MoreHorizIcon />
        <TooltipList
          show={showTooltip}
          onClose={() => {
            setShowTooltip();
          }}
        />
      </button>
    </div>
  );
}

TooltipList.js

import React, { useEffect, useRef } from "react";
import Tooltip from "react-power-tooltip";

const options = [
  {
    id: "edit",
    label: "Edit"
  },
  {
    id: "view",
    label: "View"
  }
];

function Tooptip(props) {
  const { show, onClose } = props;

  const containerRef = useRef();
  useEffect(() => {
    if (show) {
      containerRef.current.focus();
    }
  }, [show]);

  return (
    <div
      style={{ display: "inline-flex" }}
      ref={containerRef}
      tabIndex={0}
      onBlur={(e) => {
        onClose();
      }}
    >
      <Tooltip
        show={show}
        position="top center"
        arrowAlign="end"
        textBoxWidth="180px"
        fontSize="0.875rem"
        fontWeight="400"
        padding="0.5rem 1rem"
      >
        {options.map((option) => {
          return (
            <div
              className="tooltop__option d-flex align-items-center w-100"
              key={option.id}
            >
              {option.icon}
              <span style={{ fontSize: "1rem" }}>{option.label}</span>
            </div>
          );
        })}
      </Tooltip>
    </div>
  );
}

export default Tooptip;

Codesandbox
=/src/App.js:572-579

This is my current tooltip.

I am using react-power-tooltip

When I click the button, I can close the tooltip.

But I want to close the tooltip when I click outside the tooltip.

How am I supposed to do it?

App.js

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  return (
    <div className="App">
      <button
        className="post-section__body__list__item__right__menu-btn"
        onClick={() => {
          setShowTooltip((x) => !x);
        }}
        style={{ position: "relative" }}
      >
        <MoreHorizIcon />
        <TooltipList show={showTooltip} />
      </button>
    </div>
  );
}

TooltipList

import React from "react";
import Tooltip from "react-power-tooltip";

const options = [
  {
    id: "edit",
    label: "Edit"
  },
  {
    id: "view",
    label: "View"
  }
];

function Tooptip(props) {
  const { show } = props;
  return (
    <Tooltip
      show={show}
      position="top center"
      arrowAlign="end"
      textBoxWidth="180px"
      fontSize="0.875rem"
      fontWeight="400"
      padding="0.5rem 1rem"
    >
      {options.map((option) => {
        return (
          <div
            className="tooltop__option d-flex align-items-center w-100"
            key={option.id}
          >
            {option.icon}
            <span style={{ fontSize: "1rem" }}>{option.label}</span>
          </div>
        );
      })}
    </Tooltip>
  );
}

export default Tooptip;

CodeSandbox:
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js

Update 1:
I update the code based on the answer.
It can now click outside to close, but if I click the button to close the tooltip, it's not working.

App.js

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useState } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  return (
    <div className="App">
      <button
        className="post-section__body__list__item__right__menu-btn"
        onClick={() => {
          setShowTooltip((x) => !x);
        }}
        style={{ position: "relative" }}
      >
        <MoreHorizIcon />
        <TooltipList
          show={showTooltip}
          onClose={() => {
            setShowTooltip();
          }}
        />
      </button>
    </div>
  );
}

TooltipList.js

import React, { useEffect, useRef } from "react";
import Tooltip from "react-power-tooltip";

const options = [
  {
    id: "edit",
    label: "Edit"
  },
  {
    id: "view",
    label: "View"
  }
];

function Tooptip(props) {
  const { show, onClose } = props;

  const containerRef = useRef();
  useEffect(() => {
    if (show) {
      containerRef.current.focus();
    }
  }, [show]);

  return (
    <div
      style={{ display: "inline-flex" }}
      ref={containerRef}
      tabIndex={0}
      onBlur={(e) => {
        onClose();
      }}
    >
      <Tooltip
        show={show}
        position="top center"
        arrowAlign="end"
        textBoxWidth="180px"
        fontSize="0.875rem"
        fontWeight="400"
        padding="0.5rem 1rem"
      >
        {options.map((option) => {
          return (
            <div
              className="tooltop__option d-flex align-items-center w-100"
              key={option.id}
            >
              {option.icon}
              <span style={{ fontSize: "1rem" }}>{option.label}</span>
            </div>
          );
        })}
      </Tooltip>
    </div>
  );
}

export default Tooptip;

Codesandbox
https://codesandbox.io/s/optimistic-morning-m9eq3?file=/src/App.js:572-579

Share Improve this question edited Sep 15, 2021 at 4:26 CCCC asked Sep 15, 2021 at 2:06 CCCCCCCC 6,52110 gold badges61 silver badges144 bronze badges 1
  • Are you going to keep the menu open when the ponent loads? – Sanish Joseph Commented Sep 15, 2021 at 2:36
Add a ment  | 

3 Answers 3

Reset to default 5

As I can see, you are using material-ui for icons, so there is an option known as ClickAwayListner within material-ui

App.js

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import ClickAwayListener from '@material-ui/core/ClickAwayListener';
import TooltipList from "./TooltipList";
import { useState } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  const handleClickAway = () => {
    setShowTooltip(false);
  }
  return (
    <div className="App">
      <ClickAwayListener onClickAway={handleClickAway}>
        <button
          className="post-section__body__list__item__right__menu-btn"
          onClick={e => {
            e.stopPropagation();
            setShowTooltip((x) => !x);
          }}
          style={{ position: "relative" }}
        >
          <MoreHorizIcon />
          <TooltipList show={showTooltip} />
        </button>
      </ClickAwayListener>
    </div>
    
  );
}

Wrap your container with ClickAwayListener

You should add a wrapper element to detect if the click is outside a ponent then the showTooltip to false at your code: codeSanbox

   import "./styles.css";
   import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
   import TooltipList from "./TooltipList";
   import { useState, useEffect, useRef } from "react";
   
   export default function App() {
     const [showTooltip, setShowTooltip] = useState(true);
     const outsideClick = (ref) => {
       useEffect(() => {
         const handleOutsideClick = (event) => {
           if (ref.current && !ref.current.contains(event.target)) {
             setShowTooltip(false);
           }
         };
         // add the event listener
         document.addEventListener("mousedown", handleOutsideClick);
       }, [ref]);
     };
     const wrapperRef = useRef(null);
     outsideClick(wrapperRef);
     return (
       <div className="App" ref={wrapperRef}>
         <button
           className="post-section__body__list__item__right__menu-btn"
           onClick={() => {
             setShowTooltip((x) => !x);
           }}
           style={{ position: "relative" }}
         >
           <MoreHorizIcon />
           <TooltipList show={showTooltip} />
         </button>
       </div>
     );
   }

Here is a crazy little idea. I wrapped your ponent in an inline-flex div and gave it focus on load. Then added an onBlur event which will hide the menu if you click anywhere else. This can be used if you don't want to give focus on any other element on the page.

https://codesandbox.io/s/epic-kapitsa-yh7si?file=/src/App.js:0-940

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  const containerRef = useRef();

  useEffect(() => {
    containerRef.current.focus();
  }, []);
  return (
    <div className="App">
      <div
         style={{ display: "inline-flex" }}
        ref={containerRef}
        tabIndex={0}
        onBlur={(e) => {
          debugger;
          setShowTooltip(false);
        }}
      >
        <button
          className="post-section__body__list__item__right__menu-btn"
          onClick={() => {
            setShowTooltip((x) => !x);
          }}
          style={{ position: "relative" }}
        >
          <MoreHorizIcon />

          <TooltipList show={showTooltip} />
        </button>
      </div>
    </div>
  );
}

Update 1:

The problem was your button click was called every time you select an item that toggles your state. I have updated the code to prevent that using a useRef that holds a value.

ToolTip:

import React from "react";
import Tooltip from "react-power-tooltip";

const options = [
  {
    id: "edit",
    label: "Edit"
  },
  {
    id: "view",
    label: "View"
  }
];

function Tooptip(props) {
  const { show, onChange } = props;
  return (
    <>
      <Tooltip
        show={show}
        position="top center"
        arrowAlign="end"
        textBoxWidth="180px"
        fontSize="0.875rem"
        fontWeight="400"
        padding="0.5rem 1rem"
      >
        {options.map((option) => {
          return (
            <div
              onClick={onChange}
              className="tooltop__option d-flex align-items-center w-100"
              key={option.id}
            >
              {option.icon}
              <span style={{ fontSize: "1rem" }}>{option.label}</span>
            </div>
          );
        })}
      </Tooltip>
    </>
  );
}

export default Tooptip;

App

import "./styles.css";
import MoreHorizIcon from "@material-ui/icons/MoreHoriz";
import TooltipList from "./TooltipList";
import { useRef, useState, useEffect, useCallback } from "react";

export default function App() {
  const [showTooltip, setShowTooltip] = useState(true);
  const [onChangeTriggered, setonChangeTriggered] = useState(false);
  const containerRef = useRef();
  const itemClicked = useRef(false);

  useEffect(() => {
    containerRef.current.focus();
  }, []);
  return (
    <div className="App">
      <div
        style={{ display: "inline-flex" }}
        ref={containerRef}
        tabIndex={0}
        onBlur={(e) => {
          debugger;
          if (!onChangeTriggered) setShowTooltip(false);
        }}
        // onFocus={() => {
        //   setShowTooltip(true);
        // }}
      >
        <button
          className="post-section__body__list__item__right__menu-btn"
          onClick={() => {
            if (!itemClicked.current) setShowTooltip((x) => !x);
            itemClicked.current = false;
          }}
          style={{ position: "relative" }}
        >
          <MoreHorizIcon />

          <TooltipList
            show={showTooltip}
            onChange={useCallback(() => {
              itemClicked.current = true;
            }, [])}
          />
        </button>
      </div>
    </div>
  );
}

https://codesandbox.io/s/epic-kapitsa-yh7si

Enjoy !!

发布评论

评论列表(0)

  1. 暂无评论