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
相关产品推荐
相关产品推荐

