【问题标题】:Hide popper of one field when opening popper of another field打开另一个字段的弹出器时隐藏一个字段的弹出器
【发布时间】:2021-09-21 19:55:23
【问题描述】:

我正在实现两个相邻的文本字段,它们具有作为按钮的结尾装饰。这些按钮切换弹出器的可见性。每个popper都有clickawaylistener,所以当鼠标在popper外面点击时popper关闭。如果第一个弹出器打开,它应该在我单击第二个文本字段装饰的按钮时关闭。问题是结束装饰停止了事件传播。我这样做是为了防止在单击装饰时发生 clickaway 事件,从而防止在切换处理程序打开 popper 时立即关闭它。

我正在考虑将 TextField 包装到 ClickAwayListener 中,但没有成功。

附:两个 TextField 都将由单独的组件呈现,我不想在它们之间共享任何道具,因为它们应该是独立的。

https://codesandbox.io/s/basictextfields-material-demo-forked-rykrx

  const [firstPopperVisible, setFirstPopperVisible] = React.useState(false);
  const [secondPopperVisible, setSecondPopperVisible] = React.useState(false);
  const firstTextFieldRef = React.useRef();
  const secondTextFieldRef = React.useRef();

  const toggleFirstPopperVisible = (e) => {
    e.stopPropagation();
    setFirstPopperVisible((prev) => !prev);
  };
  const handleFirstPopperClickAway = (e) => {
    setFirstPopperVisible(false);
  };
  const toggleSecondPopperVisible = (e) => {
    e.stopPropagation();
    setSecondPopperVisible((prev) => !prev);
  };
  const handleSecondPoppertClickAway = (e) => {
    setSecondPopperVisible(false);
  };
  return (
    <div style={{ display: "flex", flexDirection: "row" }}>
      <div>
        <TextField
          label="Outlined"
          variant="outlined"
          inputRef={firstTextFieldRef}
          InputProps={{
            endAdornment: (
              <InputAdornment position="end">
                <IconButton
                  aria-label="toggle password visibility"
                  edge="end"
                  onClick={toggleFirstPopperVisible}
                >
                  <Visibility />
                </IconButton>
              </InputAdornment>
            )
          }}
        />
        <ClickAwayListener onClickAway={handleFirstPopperClickAway}>
          <Popper
            open={firstPopperVisible}
            anchorEl={firstTextFieldRef.current}
            placement="bottom-start"
          >
            Content
          </Popper>
        </ClickAwayListener>
      </div>
      <div>
        <TextField
          label="Outlined"
          variant="outlined"
          inputRef={secondTextFieldRef}
          InputProps={{
            endAdornment: (
              <InputAdornment position="end">
                <IconButton
                  aria-label="toggle password visibility"
                  edge="end"
                  onClick={toggleSecondPopperVisible}
                >
                  <Visibility />
                </IconButton>
              </InputAdornment>
            )
          }}
        />
        <ClickAwayListener onClickAway={handleSecondPoppertClickAway}>
          <Popper
            open={secondPopperVisible}
            anchorEl={secondTextFieldRef.current}
            placement="bottom-start"
          >
            Content
          </Popper>
        </ClickAwayListener>
      </div>
    </div>
  );
}

编辑:通过将 TextField 包装到 div 然后包装 tat ClickawayListener 找到了一个临时解决方案。还可以在需要时防止在 popper 本身上传播。这并不理想,但对我来说它有效。

<ClickAwayListener onClickAway={handleFirstPopperClickAway}>
<div style={{display: inline-box}>
<TextField>
.....
</TextField>
</div>
</ClickawayListener>
<Popper>
....
</Popper>

【问题讨论】:

  • 您只是想切换输入内容的可见性——还是想在输入之外显示内容?
  • 我想在输入下方显示自定义项目选择器,以便用户可以将信息写入输入以及从输入下方的列表(popper)中选择。

标签: reactjs material-ui


【解决方案1】:

更新

ClickAwayListener 包装在条件语句中:

        {firstPopperVisible && (
          <ClickAwayListener onClickAway={handleFirstPopperClickAway}>
            <Popper open={firstPopperVisible} anchorEl={firstTextFieldRef.current} placement="bottom-start">
              Content
            </Popper>
          </ClickAwayListener>
        )}
       ...
       {secondPopperVisible && (
          <ClickAwayListener onClickAway={handleSecondPoppertClickAway}>
            <Popper open={secondPopperVisible} anchorEl={secondTextFieldRef.current} placement="bottom-start">
              Content
            </Popper>
          </ClickAwayListener>
        )}

Codesandbox Demo

以前的

我建议您查看Portals。您可以根据需要在需要的地方添加一个,而不是在 dom 中拥有多个元素。

Portals 提供了一种将子节点渲染到 DOM 节点的一流方法 存在于父组件的 DOM 层次结构之外。

您的单一门户组件:

import ReactDOM from 'react-dom';
import React, { useEffect, useState } from 'react';

const Component = ({ content, handleCloseClick }) => {
  return <div onClick={handleCloseClick}>{content}</div>;
};

interface PortalProps {
  isShowing: boolean;
  content: any;
  location: any;
  handleCloseClick: () => void;
}

const Portal = ({ isShowing, content, handleCloseClick, location }: PortalProps) => (isShowing ? ReactDOM.createPortal(<Component handleCloseClick={handleCloseClick} content={content} />, location.current) : null);

export default Portal;

然后在您的主要组件中使用一次:

import React, { useState, useRef } from 'react';
import Widget from './Widget';

import './styles.css';

export default function App() {
  const [isShowing, setIsShowing] = useState<boolean>(false);
  const [content, setContent] = useState<string>();
  const buttonRef = useRef(null);

  const handleClick = (e) => {
    const { target } = e;

    buttonRef.current = e.target.parentNode;
    setContent(target.dataset.content);
    setIsShowing(true);
  };

  const handleCloseClick = () => {
    buttonRef.current = null;
    setContent('');
    setIsShowing(false);
  };

  return (
    <div className="App">
      <div>
        <button onClick={handleClick} data-content={`content for one`}>
          One
        </button>
      </div>
      <div>
        <button onClick={handleClick} data-content={`content for two`}>
          Two
        </button>
      </div>
      <Widget isShowing={isShowing} location={buttonRef} content={content} handleCloseClick={handleCloseClick} />
    </div>
  );
}

Codesandbox Demo

【讨论】:

  • 这可行,但在我的情况下,我的两个组件持有按钮、输入和内容是独立的,因此移动内容位置不是一种选择。就像您在两个单独的字段中选择一个日期和另一个日期的日期范围字段一样。我不想让两个弹出器同时打开,因为它们相互重叠。我无法关闭第一个单击第二个按钮的按钮,因为第一个单击监听器需要从第二个事件按钮单击传播的鼠标事件。删除 e.stopPropagation 会触发两个组件的 clickaway 侦听器,并在第二个组件打开的同时关闭它。
  • 为您的场景更新了答案,您只需要一个基本的条件渲染。我认为您可以删除stopPropagation()
  • 感谢更新。但仍然如前所述,第一个 poppers 不会共享或接收来自其他 poppers 的任何状态或参考。主要问题是独立的,无论它是否是单一的popper。点击结束装饰同时作为 onclick 和 clickaway 事件,停止装饰点击事件的传播将防止页面中的其他弹出器关闭。我通过使用 clickawaylistener 而不是 popper 将 TextField 与 end 装饰包装起来解决了我的问题,并停止在 popper 本身上传播点击事件。
猜你喜欢
  • 1970-01-01
  • 2012-12-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多