React聊天按钮重复添加FAQ问题列表的问题排查与修复
问题原因与修复方案
问题原因
- 重复追加DOM元素:每次点击聊天图标时,都会执行
chatBox.appendChild(questionsList)向聊天窗口添加FAQ列表,但关闭窗口仅通过display: none隐藏,并未移除已添加的列表元素,再次打开时会重复追加,导致问题数量翻倍。 - 不符合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
相关产品推荐
相关产品推荐

