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

React中使用querySelector报错#5不是有效选择器的问题求助

解决querySelectorAll选择器无效错误及React组件优化方案

问题描述

开发NotificationPopUpComponent组件时,点击类名为accept的按钮完成API请求后,尝试用querySelectorAll选中对应id的两个<span>元素并移除,触发错误:

ERROR Failed to execute 'querySelectorAll' on 'Document': '#5' is not a valid selector.

组件代码:

import { useContext, useEffect, useState } from "react";
import { HomeContext } from "../../contexts/contexHome";
import { Api } from "../../services/api";
import { NotificationPopUpComponentStyle } from "../../styles/NotificationPopUpComponent";

export const NotificationPopUpComponent = () => {
  const { userSave }:any = useContext(HomeContext);

  const [foundRequests, setFoundRequests] = useState<any>([]);

  const handleInvitationClick = (data: any) => {
    let token: any = localStorage.getItem('access')
    token = JSON.parse(token)

    if(data.target.classList.value == 'accept') {
      
      const acceptFriend = async () => {
        const accepted = {accepted: true, pendding: false}
        const result = await Api.patch(`/users/friend/${data.target.id}/`, accepted, {headers: {Authorization: `Bearer ${token}`}})

        const userWantedToFollow = { user_id: Number(data.target.id) };

        const followFriend = await Api.post("/users/follow/", userWantedToFollow, {
          headers: { Authorization: `Bearer ${token}` },
      })
      }
      acceptFriend()

      const acceptBtn = document.querySelectorAll(`#${data.target.id}`)
      acceptBtn.forEach(e => {
        e.remove()
      })
    }
  }

  useEffect(() => {
    if (userSave && userSave.pendding_requests && userSave.pendding_requests.length > 0) {
      const getEachUserInfo = async () => {
        const userInfo = userSave.pendding_requests.map(async (e: any) => {
          const result = await Api.get(`http://127.0.0.1:8000/api/users/${e.friend}/`);
          return result;
        });

        const resolvedPromises: any = await Promise.all(userInfo);
        setFoundRequests(resolvedPromises);
        // console.log(resolvedPromises)
      };

      getEachUserInfo();
    }
  }, [userSave]);

  return (
    <NotificationPopUpComponentStyle>
      {foundRequests.length > 0 &&
        foundRequests.map((e: any) => {
            return <div className="userRequestContainer">
                        
                        <img src={e.data.perfil} alt="" />
                            
                        <div className="buttonsContainer">
                            <span id={e.data.id} className="accept" onClick={(event) => handleInvitationClick(event)}>Aceitar</span>
                            <span id={e.data.id} className="deny" onClick={(event) => handleInvitationClick(event)}>Recusar</span>
                        </div>
                    </div>
          
      })}
    </NotificationPopUpComponentStyle>
  );
};

错误栈信息:

ERROR Failed to execute 'querySelectorAll' on 'Document': '#5' is not a valid selector.
at handleInvitationClick (http://localhost:3000/main.df85e04188aaf6d4ee96.hot-update.js:60:34)
at onClick (http://localhost:3000/main.df85e04188aaf6d4ee96.hot-update.js:97:31)
at HTMLUnknownElement.callCallback (http://localhost:3000/static/js/bundle.js:19657:18)
at Object.invokeGuardedCallbackDev (http://localhost:3000/static/js/bundle.js:19701:20)
at invokeGuardedCallback (http://localhost:3000/static/js/bundle.js:19758:35)
at invokeGuardedCallbackAndCatchFirstError (http://localhost:3000/static/js/bundle.js:19772:29)
at executeDispatch (http://localhost:3000/static/js/bundle.js:23916:7)
at processDispatchQueueItemsInOrder (http://localhost:3000/static/js/bundle.js:23942:11)
at processDispatchQueue (http://localhost:3000/static/js/bundle.js:23953:9)
at dispatchEventsForPlugins (http://localhost:3000/static/js/bundle.js:23962:7)

解决方案

1. 错误根源

CSS选择器规范中,ID不能以纯数字开头(或直接为数字),#5属于无效选择器,导致querySelectorAll执行失败。此外,React不推荐直接操作DOM,应该通过状态驱动视图更新。

2. 正确实现方案

通过状态管理控制元素显示,同时修复重复ID问题:

修改后的完整代码

import { useContext, useEffect, useState } from "react";
import { HomeContext } from "../../contexts/contexHome";
import { Api } from "../../services/api";
import { NotificationPopUpComponentStyle } from "../../styles/NotificationPopUpComponent";

// 定义类型替代any,提升代码可读性
interface UserRequest {
  data: {
    id: number;
    perfil: string;
  };
  isAccepted?: boolean;
}

export const NotificationPopUpComponent = () => {
  const { userSave } = useContext(HomeContext);

  const [foundRequests, setFoundRequests] = useState<UserRequest[]>([]);

  const handleInvitationClick = async (userId: number, action: 'accept' | 'deny') => {
    const token = JSON.parse(localStorage.getItem('access') || '""');
    if (!token) return;

    try {
      if (action === 'accept') {
        // 接受好友请求
        await Api.patch(`/users/friend/${userId}/`, 
          { accepted: true, pendding: false }, 
          { headers: { Authorization: `Bearer ${token}` } }
        );
        // 关注用户
        await Api.post("/users/follow/", 
          { user_id: userId }, 
          { headers: { Authorization: `Bearer ${token}` } }
        );
      }

      // 更新状态,隐藏当前用户的操作按钮
      setFoundRequests(prev => 
        prev.map(request => 
          request.data.id === userId 
            ? { ...request, isAccepted: true } 
            : request
        )
      );
    } catch (error) {
      console.error('操作失败:', error);
      // 这里可以添加错误提示逻辑
    }
  };

  useEffect(() => {
    const getEachUserInfo = async () => {
      if (!userSave?.pendding_requests?.length) return;

      const userInfoPromises = userSave.pendding_requests.map(async (e: any) => {
        const result = await Api.get(`http://127.0.0.1:8000/api/users/${e.friend}/`);
        return { ...result, isAccepted: false };
      });

      const resolvedPromises = await Promise.all(userInfoPromises);
      setFoundRequests(resolvedPromises);
    };

    getEachUserInfo();
  }, [userSave]);

  return (
    <NotificationPopUpComponentStyle>
      {foundRequests.length > 0 &&
        foundRequests.map((request) => (
          <div key={request.data.id} className="userRequestContainer">
            <img src={request.data.perfil} alt="用户头像" />
            {/* 仅当未接受时显示按钮组 */}
            {!request.isAccepted && (
              <div className="buttonsContainer">
                {/* 用唯一的id或直接传递userId,避免重复ID */}
                <span 
                  id={`accept-${request.data.id}`} 
                  className="accept" 
                  onClick={() => handleInvitationClick(request.data.id, 'accept')}
                >
                  Aceitar
                </span>
                <span 
                  id={`deny-${request.data.id}`} 
                  className="deny" 
                  onClick={() => handleInvitationClick(request.data.id, 'deny')}
                >
                  Recusar
                </span>
              </div>
            )}
          </div>
        ))}
    </NotificationPopUpComponentStyle>
  );
};

3. 关键优化点

  • 状态驱动视图:通过isAccepted状态控制按钮组的显示/隐藏,完全避免直接操作DOM。
  • 唯一ID:为每个按钮生成唯一ID(如accept-5),符合HTML规范。
  • 异步处理优化:将handleInvitationClick改为异步函数,等待API请求完成后再更新状态,避免状态与DOM不一致。
  • 错误处理:添加try-catch捕获API请求错误,提升鲁棒性。
  • 类型规范:定义UserRequest接口替代any,提升代码可维护性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 07:24:51