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

React聊天按钮重复添加FAQ问题列表的问题排查与修复

问题原因与修复方案

问题原因

  1. 重复追加DOM元素:每次点击聊天图标时,都会执行chatBox.appendChild(questionsList)向聊天窗口添加FAQ列表,但关闭窗口仅通过display: none隐藏,并未移除已添加的列表元素,再次打开时会重复追加,导致问题数量翻倍。
  2. 不符合React范式:代码直接操作DOM(如document.querySelector、appendChild),而非利用React的状态管理控制UI渲染,不仅导致重复渲染问题,还会引发事件重复绑定等潜在bug。

修复建议

改用React的状态管理(useState)控制聊天窗口的显隐,通过JSX直接渲染FAQ列表,彻底避免手动操作DOM带来的问题:

完整修复代码

import React, { useState } from 'react'
import chat from "../assets/images/chat-icon.png"

// FAQ数据放在组件外部,避免重复创建
const faq = [
  {
    question: "How fast can an electric bike go?",
    answer: "The speed of an E-Bike depends on the model and the motor it is equipped with. Generally, E-Bikes can go up to 30 kph or more, depending on the laws in your area. Nayel 3.8E can go up to 100 kph."
  },
  {
    question: "How long does the battery last?",
    answer: "The battery life of an E-Bike varies depending on the model and how much you use the motor. On average, a fully charged battery can last between 30 to 60 kms. Nayel 3.8E goes 100 km on a single charge (conditions apply)."
  },
  {
    question: "Is license required for electric bikes?",
    answer: "Yes, a valid license is required for electric bikes in Pakistan."
  },
  {
    question: "For further assistant, click here",
    answer: "Feel free to contact us on contact@aim-motors.com for further queries."
  }
];

const ChatIcon = () => {
  // 状态管理:聊天窗口显隐、聊天消息列表、未读消息数
  const [isChatOpen, setIsChatOpen] = useState(false);
  const [chatMessages, setChatMessages] = useState([]);
  const [unreadCount, setUnreadCount] = useState(0);

  // 获取对应问题的答案
  const getAnswer = (question) => {
    const match = faq.find(item => item.question.toLowerCase() === question.toLowerCase());
    return match ? match.answer : "I'm sorry, I don't understand. Please try asking a different question.";
  };

  // 发送消息(点击FAQ问题时触发)
  const sendMessage = (question) => {
    const newMessages = [...chatMessages, { type: 'question', content: question }];
    setChatMessages(newMessages);
    
    setTimeout(() => {
      const answer = getAnswer(question);
      const updatedMessages = [...newMessages, { type: 'answer', content: answer }];
      setChatMessages(updatedMessages);
      setUnreadCount(prev => prev + 1);
      
      // 保存到本地存储
      const chatHistory = JSON.parse(localStorage.getItem('chatHistory') || '[]');
      chatHistory.push({ question, answer });
      localStorage.setItem('chatHistory', JSON.stringify(chatHistory));
    }, 1000);
  };

  // 关闭聊天窗口
  const hideChatBox = () => {
    setIsChatOpen(false);
    setUnreadCount(0);
  };

  // 打开聊天窗口
  const openChatBox = () => {
    setIsChatOpen(true);
  };

  return (
    <div className="chat">
      {/* 聊天图标 */}
      <div 
        className="chat-icon" 
        style={{ display: isChatOpen ? 'none' : 'block' }}
        onClick={openChatBox}
      >
        <img src={chat} alt="Chat Icon" id="chat-icon-img" />
        {unreadCount > 0 && (
          <div id="chat-badge" style={{ display: 'block' }}>
            {unreadCount > 99 ? '99+' : unreadCount}
          </div>
        )}
      </div>

      {/* 聊天窗口 */}
      <div id="chat-box" style={{ display: isChatOpen ? 'block' : 'none' }}>
        <div id="chat-header">
          <span>EV Sales Assistant</span>
          <button id="close-btn" onClick={hideChatBox}>x</button>
        </div>
        <div id="chat-body">
          {/* 渲染聊天消息 */}
          {chatMessages.map((msg, index) => (
            <div 
              key={index} 
              className={`message ${msg.type === 'question' ? 'user-message' : 'bot-message'}`}
            >
              {msg.content}
            </div>
          ))}
        </div>
        {/* FAQ列表区域 */}
        <div className="questions-container" style={{ 
          maxHeight: faq.length > 4 ? '150px' : 'auto',
          overflowY: faq.length > 4 ? 'scroll' : 'visible'
        }}>
          <ul className="questions-list">
            {faq.map((item, index) => (
              <li 
                key={index} 
                className="faq-question"
                onClick={() => sendMessage(item.question)}
              >
                {item.question}
              </li>
            ))}
          </ul>
        </div>
      </div>
    </div>
  );
}

export default ChatIcon

关键修改点

  • 使用useState管理聊天窗口的显隐状态(isChatOpen),替代直接修改DOM样式。
  • 通过JSX直接渲染FAQ列表和聊天消息,完全移除手动操作DOM的代码。
  • 将FAQ数据放在组件外部,避免每次渲染重复创建数组。
  • 用React状态管理聊天消息和未读计数,逻辑更清晰且符合React设计理念。
  • 移除重复的事件绑定,所有事件通过JSX的onClick属性绑定,避免多次绑定导致的异常。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 07:50:55