Ссылка на видео отображается только после обновления функциональные компоненты не могут быть указаны ссылки

#reactjs #next.js #use-ref

Вопрос:

У меня есть СЛЕДУЮЩЕЕ приложение JS, которое использует следующий VideoPlayer компонент:

 import React, { useCallback, useEffect, useState, useContext } from "react";  import { useSocket, useSocketUpdate } from "../Store"; import { useRouter } from "next/router";  import styles from "../styles/CallPage.module.css";  const VideoPlayer = (props: any) =gt; {  const {  isAdmin,  myVideo,  userVideo  }: any = useSocket();  const router = useRouter();  useEffect(() =gt; {  if (router.isReady amp;amp; myVideo.current amp;amp; userVideo.current) {  myVideo?.current.load();  userVideo?.current.load();  }  }, []);   return (  lt;div className={styles.grid_container}gt;  {stream amp;amp; (  lt;divgt;  lt;video  className={styles.video}  playsInline  muted  ref={myVideo}  autoPlay  /gt;  lt;div className={styles.overlay}gt;  lt;h2 className={styles.overlay_h2}gt;OVERLAYlt;/h2gt;  lt;/divgt;  lt;/divgt;  )}  {callAccepted amp;amp; !callEnded ? (  lt;divgt;  lt;video  className={styles.video}  playsInline  ref={userVideo}  autoPlay  /gt;  lt;div className={styles.overlay}gt;  lt;h2 className={styles.overlay_h2}gt;OVERLAY2lt;/h2gt;  lt;/divgt;  lt;/divgt;  ) : (  isGuest amp;amp;  !callAccepted amp;amp; (  lt;div className={styles.grid_container}gt;  lt;div className={styles.no_match_content}gt;  lt;h2 className={styles.h2_no_match}gt;  Please wait until the host of this room let you in.  lt;/h2gt;  lt;div className={styles.btn_no_match}gt;lt;/divgt;  lt;/divgt;  lt;/divgt;  )  )}  lt;/divgt;  ); };  export default VideoPlayer;  

Ссылка на MyVideo извлекается с помощью крючка useSocket, который взят из следующего Store объявления:

 import React, {  createContext,  useState,  useRef,  useEffect,  useContext } from "react"; import { io } from "socket.io-client"; import Peer from "simple-peer";  import { useRouter } from "next/router";   const SocketContext = createContext({}); const SocketUpdateContext = createContext({});  const socket = io(`${process.env.NEXT_PUBLIC_SERVER_URL}`);   function useSocket() {  const useSocket = useContext(SocketContext);  if (useSocket === undefined) {  throw new Error("useSocket must be used within a SocketProvider");  }  return useSocket; }  function useSocketUpdate() {  const useSocketUpdate = useContext(SocketUpdateContext);  if (useSocketUpdate === undefined) {  throw new Error("useSocketUpdate must be used within a SocketProvider");  }  return useSocketUpdate; }  const ContextProvider = ({ children }: any) =gt; {  const router = useRouter();  const [callAccepted, setCallAccepted] = useState(false);  const [callEnded, setCallEnded] = useState(false);  const [stream, setStream] = useStatelt;MediaStreamgt;();  const [name, setName] = useState("");  const [call, setCall] = useStatelt;anygt;({});  const [me, setMe] = useState("");  const [isAdmin, setAdmin] = useState(false);  const [isGuest, setGuest] = useState(false);  const [idToCall, setIdToCall] = useState("");   const myVideo = useReflt;HTMLVideoElementgt;(null!);  const userVideo = useReflt;HTMLVideoElementgt;(null!);  const connectionRef = useReflt;Peer.Instancegt;(null!);   useEffect(() =gt; {  navigator.mediaDevices  .getUserMedia({ video: true, audio: true })  .then((currentStream) =gt; {  setStream(currentStream);  if (myVideo.current) {  myVideo.current.srcObject = currentStream;  }  });   socket.on("me", (id) =gt; {  setMe(id);  });   socket.on("callUser", ({ from, name: callerName, signal }) =gt; {  setCall({ isReceivingCall: true, from, name: callerName, signal });  });  }, []);   const answerCall = () =gt; {  setCallAccepted(true);   const peer = new Peer({ initiator: false, trickle: false, stream });   peer.on("signal", (data) =gt; {  socket.emit("answerCall", { signal: data, to: call.from });  });   peer.on("stream", (currentStream) =gt; {  if (userVideo.current) userVideo.current.srcObject = currentStream;  });   peer.signal(call.signal);   connectionRef.current = peer;  };   const callUser = (id: String) =gt; {  // setAdmin(false);  const peer = new Peer({ initiator: true, trickle: false, stream });   peer.on("signal", async (data) =gt; {  socket.emit("callUser", {  userToCall: id,  signalData: data,  from: me,  name  });  });   peer.on("stream", (currentStream) =gt; {  if (userVideo.current) userVideo.current.srcObject = currentStream;  });   socket.on("callAccepted", (signal) =gt; {  setCallAccepted(true);   peer.signal(signal);  });  if (connectionRef.current) connectionRef.current = peer;  };   const leaveCall = () =gt; {  setCallEnded(true);  setAdmin(false);  if (connectionRef.current) connectionRef.current.destroy();  // if (typeof window !== "undefined") window.location.reload();  router.push("/home");  };   return (  lt;SocketContext.Provider  value={{  call,  callAccepted,  myVideo,  userVideo,  stream,  name,  callEnded,  me,  isAdmin,  router,  isGuest,  user,  isAuthenticated,  isUnauthenticated,  isAuthenticating,  authError,  idToCall  }}  gt;  lt;SocketUpdateContext.Provider  value={{  callUser,  leaveCall,  answerCall,  setName,  setAdmin,  setGuest,  logout,  authenticate,  signup,  login,  setIdToCall  }}  gt;  {children}  lt;/SocketUpdateContext.Providergt;  lt;/SocketContext.Providergt;  ); };  export { ContextProvider, SocketContext, useSocket, useSocketUpdate };  

And finally Im rendering the VideoPlayer component in this CallPage :

 import { useEffect, useReducer, useState, useContext } from "react"; import styles from "../styles/CallPage.module.css"; import Peer from "simple-peer"; import VideoPlayer from "../components/VideoPlayer";  import Page from "../components/UI/Page"; import Link from "next/link"; import Head from "next/head";  import { useSocket, useSocketUpdate } from "../Store"; const initialState = [];  const CallPage = () =gt; {  const { isAdmin, myVideo, isAuthenticated, isUnauthenticated, user }: any =  useSocket();  if (isUnauthenticated) {  return (  lt;Pagegt;  lt;div className={styles.no_match_content}gt;  lt;h2 className={styles.h2_no_match}gt;  Please login first to access a call.  lt;/h2gt;  lt;div className={styles.btn_no_match}gt;  lt;Link passHref href="/login"gt;  Login  lt;/Linkgt;  lt;/divgt;  lt;/divgt;  lt;/Pagegt;  );  }  let alertTimeout = null;   const [messageList, messageListReducer] = useReducer(  MessageListReducer,  initialState  );   useEffect(() =gt; {  if (isAdmin) {  setMeetInfoPopup(true);  }  }, []);   return (  lt;div className={styles.page_container}gt;  lt;VideoPlayer className={styles.video_container} /gt;  )}  lt;/divgt;  ); }; export default CallPage;  

Когда я нажимаю на свой дом, чтобы перейти на страницу вызовов, делая это:

 lt;Link  passHref  href="/callpage"  gt;  lt;Button  loadingText="Loading"  colorScheme="teal"  variant="outline"  spinnerPlacement="start"  className={styles.btn}  onClick={handleAdmin}  gt;  New Meeting  lt;/Buttongt;  lt;/Linkgt;  

Маршрутизатор берет меня, и все отображается, кроме видеоряда. Если я обновлю страницу, она загрузится. У вас есть какие-нибудь идеи, почему это происходит? Devtools выдает ошибку, в которой говорится, что

функциональным компонентам не могут быть даны ссылки

даже несмотря на то, что я использую такую ссылку в элементе DOM, как вы можете видеть. Поэтому я уже исключил использование forwardref. Я был бы очень признателен за любую помощь. Я тут ничего не понимаю.

Комментарии:

1. Функциональные компоненты не принимают ссылки React, вам необходимо переслать все переданные ссылки React. Какому компоненту(компонентам) передаются ссылки, и какая ссылка это?

Ответ №1:

Одна идея…

 useEffect(() =gt; {   if (router.isReady amp;amp; myVideo.current amp;amp; userVideo.current) {  myVideo?.current.load();  userVideo?.current.load();  } }, []);  

У вашего крючка есть пустой массив зависимостей. Конечно, возможно, что при первом отображении router.isReady результаты будут ложными. Вы должны перейти router.isReady к массиву зависимостей.

Кроме того, ваша функция CallPage условно запускает эффект. Этот эффект:

 useEffect(() =gt; {  if (isAdmin) {  setMeetInfoPopup(true);  } }, []);   

следует также передать isAdmin и независимо от ЗНАЧЕНИЯ setMeetInfoPopup задатчика в массив зависимостей. Предполагая useState , что значение для этого сеттера вызывается meetInfoPopup , крючок на самом деле должен выглядеть так:

 useEffect(() =gt; {  if (isAdmin amp;amp; !meetInfoPopup) {  setMeetInfoPopup(true);  } }, [isAdmin, meetInfoPopup]);