如何修复React中Cannot read properties of undefined (reading 'classList')错误
React ChildrenList组件鼠标进入事件错误修复方案
问题场景
在React开发的ChildrenList组件中,触发鼠标进入事件时抛出错误:
TypeError: Cannot read properties of undefined (reading 'classList')
错误定位在handleOnMouseEnterDetailButton函数,组件代码如下:
import { useCollectionData } from "react-firebase-hooks/firestore"; import { collection, deleteDoc, doc } from "firebase/firestore"; import db from "../../firebase"; import { useGlobalContext } from "../../context"; import styles from "./childreList.module.css"; import { PiDotsThreeOutlineVerticalDuotone } from "react-icons/pi"; import { GoTrash } from "react-icons/go"; const ChildrenList = ({ path }) => { const { value, categoryName } = useGlobalContext(); const query = collection(db, path); const [docs, loading, error] = useCollectionData(query); const handleDeleteSubCollection = async (uuid) => { await deleteDoc( doc(db, `users/${value.uid}/children/${categoryName}/children2/${uuid}`) ); }; const handleOnMouseLeaveDetailButton = (e) => { e.target.children[1].classList.remove(styles.detail_button_active); }; const handleOnMouseEnterDetailButton = (e) => { e.target.children[1].classList.add(styles.detail_button_active); console.log(e.target.children); }; const handleOnClickAdd = (e) => { console.log(e.target.previousSibling); e.target.nextSibling.classList.add(styles.delete_button_active); }; const handleOnMouseLeaveRemove = (e) => { e.target.nextSibling.classList.remove(styles.delete_button_active); }; return ( <div className={styles.container}> {docs?.map((item, index) => { return ( <div onMouseEnter={handleOnMouseEnterDetailButton} onMouseLeave={handleOnMouseLeaveDetailButton} className={styles.container_single_card} key={index} > <div className={styles.word_part}> {item.englishWord} {/* {item.turkishWord} */} </div> {/* details */} <button className={styles.detail_button} onClick={handleOnClickAdd} onMouseLeave={handleOnMouseLeaveRemove} > <PiDotsThreeOutlineVerticalDuotone /> </button> {/* delete */} <button className={styles.delete_button} onClick={() => handleDeleteSubCollection(item.uuid)} > <GoTrash /> </button> </div> ); })} </div> ); }; export default ChildrenList;
错误原因
e.target指向不固定:鼠标进入外层容器时,e.target可能指向内部的文本区块、按钮甚至图标元素,而非绑定事件的外层容器。此时e.target.children[1]会因目标元素没有足够子元素返回undefined,访问classList时就会抛出错误。- DOM结构依赖过强:直接通过索引获取子元素的写法,完全依赖固定的DOM层级,一旦页面结构调整(比如新增元素),代码直接失效。
修复方案
方案一:使用React状态管理(推荐)
遵循React数据驱动的理念,用状态控制类名,放弃直接操作DOM:
import { useCollectionData } from "react-firebase-hooks/firestore"; import { collection, deleteDoc, doc } from "firebase/firestore"; import db from "../../firebase"; import { useGlobalContext } from "../../context"; import styles from "./childreList.module.css"; import { PiDotsThreeOutlineVerticalDuotone } from "react-icons/pi"; import { GoTrash } from "react-icons/go"; import { useState } from "react"; const ChildrenList = ({ path }) => { const { value, categoryName } = useGlobalContext(); const query = collection(db, path); const [docs, loading, error] = useCollectionData(query); // 记录当前hover的卡片索引 const [hoverIndex, setHoverIndex] = useState(-1); // 记录当前激活删除按钮的索引 const [activeDeleteIndex, setActiveDeleteIndex] = useState(-1); const handleDeleteSubCollection = async (uuid) => { await deleteDoc( doc(db, `users/${value.uid}/children/${categoryName}/children2/${uuid}`) ); }; return ( <div className={styles.container}> {docs?.map((item, index) => { const isHovered = hoverIndex === index; const isDeleteActive = activeDeleteIndex === index; return ( <div onMouseEnter={() => setHoverIndex(index)} onMouseLeave={() => setHoverIndex(-1)} className={styles.container_single_card} key={index} > <div className={styles.word_part}> {item.englishWord} {/* {item.turkishWord} */} </div> {/* details */} <button className={`${styles.detail_button} ${isHovered ? styles.detail_button_active : ''}`} onClick={() => setActiveDeleteIndex(index)} onMouseLeave={() => setActiveDeleteIndex(-1)} > <PiDotsThreeOutlineVerticalDuotone /> </button> {/* delete */} <button className={`${styles.delete_button} ${isDeleteActive ? styles.delete_button_active : ''}`} onClick={() => handleDeleteSubCollection(item.uuid)} > <GoTrash /> </button> </div> ); })} </div> ); }; export default ChildrenList;
方案二:修正事件目标,确保操作正确DOM元素
如果坚持直接操作DOM,需要确保始终获取到绑定事件的外层容器,同时处理按钮内部图标的目标指向问题:
// 修改鼠标进入/离开容器的处理函数 const handleOnMouseLeaveDetailButton = (e) => { // currentTarget始终指向绑定事件的外层容器 e.currentTarget.children[1].classList.remove(styles.detail_button_active); }; const handleOnMouseEnterDetailButton = (e) => { e.currentTarget.children[1].classList.add(styles.detail_button_active); }; // 修改按钮相关处理函数,避免target指向内部图标 const handleOnClickAdd = (e) => { const button = e.target.closest('button'); if (button) { button.nextSibling.classList.add(styles.delete_button_active); } }; const handleOnMouseLeaveRemove = (e) => { const button = e.target.closest('button'); if (button) { button.nextSibling.classList.remove(styles.delete_button_active); } };
内容的提问来源于stack exchange,提问作者Utku Ergün
相关产品推荐
相关产品推荐

