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

React视频流应用中Invalid hook call错误排查求助

解决基于Agora的React视频流应用中的「Invalid hook call」错误

错误原因

你遇到的「Invalid hook call」错误,核心原因是违反了React Hook的调用规则:在模块顶层(Settings.js)直接执行了createMicrophoneAndCameraTracks(),而Hook只能在React函数组件或自定义Hook的顶层调用,不能在模块级代码中执行初始化。

解决方案

步骤1:修正Settings.js的Hook创建逻辑

将直接初始化Hook的代码,改为导出创建Hook的函数,避免在模块顶层触发Hook逻辑:

import { createClient, createMicrophoneAndCameraTracks } from "agora-rtc-react";

const appId = "d13f5075f00d417092285bc8a52f17f2";
const token = "007eJxTYOjZ/X7lClbPsGXOv7vW/egUYDx+N+WSl9Bzddlf2fyLD5orMKQYGqeZGpibphkYpJgYmhtYGhlZmCYlWySaGqUZmqcZKT+bk9wQyMiwSdaKkZEBAkF8FobcxMw8BgYAbJIfFw=="

export const config = { mode: "rtc", codec: "vp8", appId: appId, token: token };
// 导出创建Client Hook的函数
export const createAgoraClient = () => createClient(config);
// 导出创建音视频Track Hook的函数
export const createAgoraTracks = () => createMicrophoneAndCameraTracks();
export const channelName = "main";

步骤2:修改VideoCall.js的Hook使用

在组件内部(或组件外部的自定义Hook层级)创建并使用Agora相关Hook:

import { useState, useEffect } from "react";
import {
  config,
  createAgoraClient,
  createAgoraTracks,
  channelName,
} from "./settings.js";
import { Grid } from "@material-ui/core";
import Video from "./Video";
import Controls from "./Controls";

// 在自定义Hook层级创建Agora相关Hook(符合规则)
const useClient = createAgoraClient();
const useMicrophoneAndCameraTracks = createAgoraTracks();

export default function VideoCall(props) {
  const { setInCall } = props;
  const [users, setUsers] = useState([]);
  const [start, setStart] = useState(false);
  const client = useClient();
  const { ready, tracks } = useMicrophoneAndCameraTracks();

  useEffect(() => {
    let init = async (name) => {
      client.on("user-published", async (user, mediaType) => {
        await client.subscribe(user, mediaType);
        if (mediaType === "video") {
          setUsers((prevUsers) => [...prevUsers, user]);
        }
        if (mediaType === "audio") {
          user.audioTrack.play();
        }
      });

      client.on("user-unpublished", (user, mediaType) => {
        if (mediaType === "audio") {
          user.audioTrack?.stop();
        }
        if (mediaType === "video") {
          setUsers((prevUsers) => prevUsers.filter(User => User.uid !== user.uid));
        }
      });

      client.on("user-left", (user) => {
        setUsers((prevUsers) => prevUsers.filter(User => User.uid !== user.uid));
      });

      try {
        await client.join(config.appId, name, config.token, null);
      } catch (error) {
        console.error("加入频道失败:", error);
      }

      tracks && await client.publish([tracks[0], tracks[1]]);
      setStart(true);
    };

    ready && tracks && init(channelName);
  }, [channelName, client, ready, tracks]);
  
  return (
    <Grid container direction="column" style={{ height: "100%" }}>
      <Grid item style={{ height: "5%" }}>
        {ready && tracks && (
          <Controls tracks={tracks} setStart={setStart} setInCall={setInCall} />
        )}
      </Grid>
      <Grid item style={{ height: "95%" }}>
        {start && tracks && <Video tracks={tracks} users={users} />}
      </Grid>
    </Grid>
  );
}

步骤3:修正Controls.js的Hook使用与拼写错误

同样在组件内创建Hook,并修正React事件名的拼写错误(onclick改为onClick):

import { useState } from 'react';    
import { createAgoraClient } from './settings';
import { Grid, Button } from '@material-ui/core';
import MicIcon from '@material-ui/icons/Mic';
import MicOffIcon from '@material-ui/icons/MicOff';
import VideocamIcon from '@material-ui/icons/Videocam';
import VideocamOffIcon from '@material-ui/icons/VideocamOff';
import ExitToAppIcon from '@material-ui/icons/ExitToApp';

const useClient = createAgoraClient();

export default function Controls(props) {
   const client = useClient();
   const {tracks, setStart, setInCall} = props;
   const [trackState, setTrackState] = useState({video: true, audio: true});

   const mute = async(type)=> {
      if(type === "audio") {
         await tracks[0].setEnabled(!trackState.audio);
         setTrackState(ps => ({...ps, audio: !ps.audio}));
      }
      else if(type === "video") {
         await tracks[1].setEnabled(!trackState.video);
         setTrackState(ps => ({...ps, video: !ps.video}));
      }
   }

   const leaveChannel = async() => {
      await client.leave();
      client.removeAllListeners();
      tracks[0].close();
      tracks[1].close();
      setStart(false);
      setInCall(false);
   }

   return (
      <Grid container spacing={2} alignItems='center'>
         <Grid item>
            <Button variant="contained" color={trackState.audio ? "primary" : "secondary"} onClick={() => mute("audio")}>
              {trackState.audio ? <MicIcon/> : <MicOffIcon/>}
            </Button>
         </Grid>
         <Grid item>
            <Button variant="contained" color={trackState.video ? "primary" : "secondary"} onClick={() => mute("video")}>
              {trackState.video ? <VideocamIcon/> : <VideocamOffIcon/>}
            </Button>
         </Grid>
         <Grid item>
            <Button variant="contained" color="default" onClick={() => leaveChannel()}>
              Leave <ExitToAppIcon/>
            </Button>
         </Grid>
      </Grid>
   )
}

关键规则提醒

React Hook必须严格遵守以下规则:

  • 仅在React函数组件的顶层调用
  • 仅在自定义Hook的顶层调用
  • 禁止在循环、条件判断、嵌套函数中调用

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 10:25:21