自动完成中的 React Material UI 打开模式失去焦点

Posted

技术标签:

【中文标题】自动完成中的 React Material UI 打开模式失去焦点【英文标题】:React Material UI open modal from within autocomplete lose focus 【发布时间】:2020-07-19 09:51:18 【问题描述】:

我正在使用 material-ui 库,我需要一个自动完成功能,其中自动完成功能中的每个项目都是可点击的并打开一个模式。

一般的结构是:

const ModalBtn = () => 
    ...
    return (
        <>
            <button ... (on click set modal to open)
            <Modal ...
        </>

    );


const AutoCompleteWithBtns = () => 
    return (
        <Autocomplete
            renderTags=(value, getTagProps) =>
                value.map((option, index) => <ModalBtn />)
            
            ...
        />
    );


注意ModalBtn是一个组件,不能分为Button和Modal两个组件。

问题是,当您单击模式内的按钮时 - 焦点保持在自动完成内,并且模式永远不会获得焦点(如果我在模式内有输入 - 我不能在里面写任何东西)。

尝试了所有标准的自动完成/模态焦点相关的道具(disableEnforceFocusdisableEnforceFocus 等...),但没有任何效果。

这是working codesandbox example。如您所见 - 如果您单击不在自动完成组件内的按钮 - 一切正常(您可以在输入字段中添加文本)。如果您单击自动完成内的按钮 - 模式内的输入字段不可编辑(您失去焦点)。

这是一个问题示例:

【问题讨论】:

你能检查一下沙箱吗,它不工作 @Sabbin 对此感到抱歉,现在应该可以了。 是的,它有效,只是有一个问题,你需要两个具有相同功能的独立模态吗?问题是您会同时打开多个吗? 应该支持多个模式,但重要的是我需要从自动完成中打开模式(我假设一旦解决了这个问题,嵌套模式也应该解决) . @RyanCogswell 确实如此 :) 谢谢!我通常会等到最后一天才能获得赏金奖励,以便更多地了解答案。 【参考方案1】:

您的代码中的问题是,Modal 是从 AutoComplete 组件的标记内呈现的,由于组件的可见性,这不是正常的,组件的层次结构是问题。

修复方法是将Modal 移动到FixedTags 组件中,并将打开的处理程序传递给renderTags 属性中的ModalBtn

我已使用工作变体 HERE 更新了您的沙盒

变化如下

const ModalBtn = ( handleOpen ) => (
  <button type="button" onClick=handleOpen>
    Open Modal (not working)
  </button>
);

const FixedTags = function() 
  const classes = useStyles();
  const [modalStyle] = React.useState(getModalStyle);
  const [open, setOpen] = React.useState(false);

  const handleOpen = () => 
    setOpen(true);
  ;

  const handleClose = () => 
    setOpen(false);
  ;

  return (
    <>
      <Autocomplete
        multiple
        options=autoCompleteItems
        getOptionLabel=option => option.title
        defaultValue=[autoCompleteItems[1], autoCompleteItems[2]]
        renderTags=(value, getTagProps) =>
          value.map((option, index) => <ModalBtn handleOpen=handleOpen />)
        
        style= width: 500 
        renderInput=params => (
          <TextField
            ...params
            label="Fixed tag"
            variant="outlined"
            placeholder="items..."
          />
        )
      />
      <Modal open=open onClose=handleClose>
        <div style=modalStyle className=classes.paper>
          <h2 style= color: "red" >This one doesn't work</h2>
          <p>Text field is not available</p>
          <TextField label="Filled" variant="filled" /> <br />
          <br />
          <br />
          <FixedTags label="Standard" />
        </div>
      </Modal>
    </>
  );
;

【讨论】:

将模式本身移到自动完成标签之外确实是一个有效的解决方案,但不是一个好的解决方案。 ModalTbn 是一个完整的(可以说是一个封闭的组件),它呈现一个按钮并在单击时打开一个模式。这两个是耦合的,我真的不想把它们分开。 实际上您的代码存在内存泄漏问题,因为假设您在一个自动完成功能中有 1000 个标签,那么您在该自动完成功能中有 1000 个模式。当您一次只能访问一个组件时,为什么要渲染 1000 个组件?假设它嵌套了 6 个级别,每个级别 1000。您将有 6000 个模态组件和 6 个渲染?您无法在任何时候从同一级别打开 2 个模态,为什么要打开它们?这就是 react 的美妙之处,每个元素都可以是一个组件本身,将按钮保留在一个组件中,将 modal 保留在另一个组件中,将状态和处理程序保留在另一个组件中...... 不用说,在 6000 个标签中,您有 12000 个处理程序和 6000 个状态对象放置在周围...... 模态框只有在打开时才会渲染(这确实是 react 的美妙之处),所以这不是问题。在任何情况下,您都需要将模态内容的数据保存在某处(即使您不显示它),所以我并不担心“内存泄漏”(我在示例中没有) .您可能会认为这是浪费内存,但不是泄漏(自动完成元素将被删除 - 按钮和模式也将被删除,并且内存将被释放)。 对话框组件确实会在打开时渲染,但是状态对象,现在我看到有2个,为什么是静态idk时状态中的样式,反正状态和处理程序是创建的每一个人。就我个人而言,我不认为像你那样做这件事的更好方法。如果有这种情况,我会寻找解决方案。在您的示例中有 2 种输入方式,或者在输入中设置 autoFocus,但如果您在外部单击它就消失了。要么右键单击它,它就会工作......不知道为什么【参考方案2】:

Autocomplete 中呈现Modal 的问题是事件从Modal 传播到Autocomplete。特别是,单击和鼠标按下事件均由Autocomplete 处理,其方式会导致您的情况出现问题。这主要是为了在您与Autocomplete 的不同部分交互时将注意力集中在正确的位置。

下面(来自https://github.com/mui-org/material-ui/blob/v4.9.11/packages/material-ui-lab/src/useAutocomplete/useAutocomplete.js#L842)是阻碍您前进的Autocomplete 代码部分:

  // Prevent input blur when interacting with the combobox
  const handleMouseDown = (event) => 
    if (event.target.getAttribute('id') !== id) 
      event.preventDefault();
    
  ;

  // Focus the input when interacting with the combobox
  const handleClick = () => 
    inputRef.current.focus();

    if (
      selectOnFocus &&
      firstFocus.current &&
      inputRef.current.selectionEnd - inputRef.current.selectionStart === 0
    ) 
      inputRef.current.select();
    

    firstFocus.current = false;
  ;

当鼠标按下事件发生在可聚焦元素上时,默认浏览器行为是让该元素接收焦点,但Autocomplete 的鼠标按下处理程序会调用event.preventDefault(),这会阻止此默认行为,从而阻止焦点更改从鼠标按下事件(因此焦点停留在Modal 本身,如其蓝色焦点轮廓所示)。但是,您可以使用 tab 键成功地将焦点移动到 Modal 的 TextField,因为没有什么可以阻止焦点更改的机制。

Autocomplete 点击处理程序将焦点放在Autocomplete 的输入上,即使您点击了Autocomplete 的其他部分。当您的Modal 打开时,这样做的效果是,当您单击Modal 时,焦点会短暂移动到Autocomplete 输入元素,但由于其“强制焦点”功能。如果将disableEnforceFocus 属性添加到Modal、you'll see 中,则当您单击Modal(例如在TextField 上)时,光标仍保留在Autocomplete 的输入中。

解决方法是确保这两个事件不会传播到Modal 之外。通过为Modal 上的单击和鼠标按下事件调用event.stopPropagation(),当这些事件在Modal 中发生时,它会阻止执行这两个事件的Autocomplete 功能。

      <Modal
        onClick=event => event.stopPropagation()
        onMouseDown=event => event.stopPropagation()
        ...

相关回答:How can I create a clickable first option in Material UI Labs Autocomplete

【讨论】:

一个精心编译和解释的答案(y)

以上是关于自动完成中的 React Material UI 打开模式失去焦点的主要内容,如果未能解决你的问题,请参考以下文章

当 React Material UI 中的 TextField 中存在值时自定义自动完成 CSS

使用 React Material-UI 自动完成始终显示默认选项

自动完成问题(Material UI + React + Reagent/ClojureScript)

如何在 onChange 后清除 Material-ui 中的自动完成输入?

移除 React Material ui 组件中自动完成的下划线样式

React/Material UI - Google Places 自动完成下拉菜单有时不起作用