在外部单击时反应关闭下拉菜单

时间:2020-08-11 13:26:03

标签: javascript reactjs

在React中构建自定义下拉菜单,并在单击外部时尝试关闭它时遇到问题。
因此,我创建了通用HOC,因此也可以将其用于其他场合。

我似乎遇到的问题是我不知道如何将ref从临时项目传递到组件。

我一直在研究forwardRef和示例的其他基调,但我想不通。
知道是否有可能以及我该怎么做?

Codesandbox Demo

import React, { useState, useRef, useEffect } from "react";

export default function withClickOutside(WrappedComponent) {
  const Component = (props) => {
    const [open, setOpen] = useState(false);

    const ref = useRef();

    useEffect(() => {
      const handleClickOutside = (event) => {
        if (!ref?.current?.contains(event.target)) {
          setOpen(false);
        }
      };
      document.addEventListener("mousedown", handleClickOutside);
    }, [ref]);

    return <WrappedComponent {...props} open={open} setOpen={setOpen} ref={ref}/>;
  };

  return Component;
}

3 个答案:

答案 0 :(得分:3)

您需要处理ref传递的内容,以便它实际附加到某物上:

const Home = forwardRef(({ open, setOpen }, ref) => {
  console.log(open);

  return (
    <section ref={ref}>
      <h1>Feels good to be home!</h1>

      <button className="secondary" onClick={() => setOpen(!open)}>
        Dropdown Toggle
      </button>

      {open && (
        <ul>
          <li>One</li>
          <li>Two</li>
        </ul>
      )}
    </section>
  );
});

Edit React withClickOutside (forked)

答案 1 :(得分:0)

您可以查看网站:

Material UI Select Component

import React from 'react';
import { makeStyles } from '@material-ui/core/styles';
import InputLabel from '@material-ui/core/InputLabel';
import MenuItem from '@material-ui/core/MenuItem';
import FormControl from '@material-ui/core/FormControl';
import Select from '@material-ui/core/Select';

const useStyles = makeStyles((theme) => ({
  formControl: {
    margin: theme.spacing(1),
    minWidth: 120,
  },
  selectEmpty: {
    marginTop: theme.spacing(2),
  },
}));

export default function SimpleSelect() {
  const classes = useStyles();
  const [age, setAge] = React.useState('');

  const handleChange = (event) => {
    setAge(event.target.value);
  };

  return (
    <div>
      <FormControl variant="filled" className={classes.formControl}>
        <InputLabel id="demo-simple-select-filled-label">Age</InputLabel>
        <Select
          labelId="demo-simple-select-filled-label"
          id="demo-simple-select-filled"
          value={age}
          onChange={handleChange}
        >
          <MenuItem value="">
            <em>None</em>
          </MenuItem>
          <MenuItem value={10}>Ten</MenuItem>
          <MenuItem value={20}>Twenty</MenuItem>
          <MenuItem value={30}>Thirty</MenuItem>
        </Select>
      </FormControl>
    </div>
  );
}

此外,您可以在CodeSandbox上看到更详细的示例

答案 2 :(得分:0)

有点区别。从您的方法,但此挂钩可用于包装您的按钮和菜单。

/**
 * This Hook can be used for detecting clicks outside the Opened Menu
 */
function clickOutside(ref, onClickOutside) {
  useEffect(() => {
    /**
     * Invoke Function onClick outside of element
     */
    function handleClickOutside(event) {
      if (ref.current && !ref.current.contains(event.target)) {
        onClickOutside();
      }
    }
    // Bind
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      // dispose
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [ref, onClickOutside]);
}

包含您的代码:

const Home = () => {
  let [open, setOpen] = useState(false);
  const wrapperRef = useRef("menu");
  outsideClickAlert(wrapperRef, () => {
    setOpen(false);
  });

  return (
    <section>
      <h1>Feels good to be home!</h1>
      <div ref={wrapperRef}>
        <button className="secondary" onClick={() => setOpen(!open)}>
          Dropdown Toggle
        </button>

        {open && (
          <ul>
            <li>One</li>
            <li>Two</li>
          </ul>
        )}
      </div>
    </section>
  );
};