You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Bootstrap Popover需点击两次才触发显示问题求解

问题原因

  1. 事件执行时序冲突:你同时给单词节点绑定了span的onClick回调和OverlayTrigger的click触发规则,两者会在点击时同步执行。第一次点击单词时,OverlayTrigger会立刻尝试弹出当前的popover实例,但此时setSelectedWord触发的状态更新是异步的,selectedWord还未变更,API请求也未返回,弹出的popover要么是旧数据,要么因状态未匹配无法正常展示。第二次点击同一单词时,selectedWord和apiData都已经更新完成,所以可以正常弹出。
  2. 所有单词共用同一个popover实例,OverlayTrigger无法感知到apiData异步更新后需要重新渲染弹出层内容。
  3. 列表渲染时key用了每次渲染都会重新生成的uuid(),会导致整个单词列表在状态更新时全量重新挂载,破坏OverlayTrigger的内部状态。

修复方案

推荐放弃OverlayTrigger的自动触发逻辑,改为手动控制Popover的显示隐藏,等API数据返回后再主动触发弹出,修改后的完整代码如下:

import React, { useState, useRef, useEffect } from "react";
import axios from "axios";
import { Alert, Popover, Overlay } from "react-bootstrap";

export default function App() {
  const [text, setText] = useState(
    "He looked at her and saw her eyes luminous with pity."
  );
  const [selectedWord, setSelectedWord] = useState("");
  const [apiData, setApiData] = useState(null);
  // 控制popover显示隐藏
  const [showPopover, setShowPopover] = useState(false);
  // 保存点击的单词节点,用于定位popover
  const [targetElement, setTargetElement] = useState(null);

  const words = text.split(/ /g);

  useEffect(() => {
    if (!selectedWord) return;
    // 每次切换单词先重置api数据,隐藏popover
    setApiData(null);
    setShowPopover(false);
    const url = "https://api.dictionaryapi.dev/api/v2/entries/en/" + selectedWord;
    axios
      .get(url)
      .then(response => {
        setApiData(response.data);
        // 数据返回后再显示popover
        setShowPopover(true);
        console.log("api call")
      })
      .catch(function (error) {
        if (error) {
          console.log("Error", error.message);
        }
      });
  }, [selectedWord]);

  function clickCallback(e, w) {
    const word = w.split(/[.!?,]/g)[0];
    // 记录点击的节点
    setTargetElement(e.currentTarget);
    setSelectedWord(word);
  }

  return (
    <Alert>
      {words.map((w, index) => (
        <span 
          key={index} 
          onClick={(e) => clickCallback(e, w)}
          style={{marginRight: '4px', cursor: 'pointer'}}
        >
          {w}
        </span>
      ))}
      {/* 手动控制的popover */}
      <Overlay
        show={showPopover}
        target={targetElement}
        placement="bottom"
        onHide={() => setShowPopover(false)}
        rootClose
      >
        <Popover id="popover-basic">
          <Popover.Body>
            {
              apiData ?
                <div>
                  <h1>{apiData[0].word}</h1>
                  <h6>{apiData[0].meanings[0].definitions[0].definition}</h6>
                </div> :
                <div>加载中...</div>
            }
          </Popover.Body>
        </Popover>
      </Overlay>
    </Alert>
  );
}

核心修改说明

  • 移除了每个单词外层的OverlayTrigger,改为全局共用一个手动控制的Popover + Overlay组件,避免多实例冲突
  • 点击单词时先记录点击的目标节点,等API请求完成后再主动弹出Popover,解决时序问题
  • 列表的key改为固定的索引(如果单词可能重复可以用index + w的组合),避免全量重渲染
  • 新增rootClose属性,点击页面其他区域可以自动关闭popover

内容的提问来源于stack exchange,提问作者cruise134

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 14:18:03