Search code examples
javascriptreactjsreact-hookswebcamgetusermedia

React Hook useRef is null when used with useEffect


I'd like to use React Hook's useRef to set the src object of a video stream but the video ref null and I get the error: TypeError: Cannot set property 'srcObject' of null at getMedia. I am using useEffect to call the function that logs the ref.

The strange part is I see both null and a value for current. See screenshot of console log:

screenshot

I have read the docs on useRef and looked at every other post on Stack Overflow and Github but cannot figure this out. The closest post is this one. What am I doing wrong?

Condensed code:

const App = () => {
  const webcamRef = useRef(null)

  useEffect(() => {
    getMedia();
  }, [webcamRef])

  const getMedia = async () => {
    try {
      console.log(webcamRef);
      let stream = await navigator.mediaDevices.getUserMedia({ video: true });
      webcamRef.current.srcObject = stream;
    } catch (err) {
      console.error(err);
    }
  };

  return <video id='webcam' ref={webcamRef} />
}

Full code: Sandbox: https://codesandbox.io/s/blissful-goldstine-rp9k5


Solution

  • Seems to be working fine on my end based on your provided code.

    I added webcamRef.current.play() to the effect in order to get the video to play.

    useEffect should always run after the component has fully mounted (/rendered), so webcamRef.current shouldn't be null when the effect runs unless video was conditionally rendered.

    Also, in the useEffect, you shouldn't have a ref in the dependency array as that does nothing and makes it look like the effect will get re-triggered when the ref changes even though it won't.

    https://codesandbox.io/s/inspiring-sara-9skhr?file=/src/App.js


    From JAM's update about conditionally rendering the video:

    The main thing you want to do is separate out the effects. You want the getMedia call to happen only when the video element is on the page, otherwise webcamRef.current can't point to anything.

    So, in this case, I made the first effect set loaded to true, then I made a second effect that used loaded as a dependency so that getMedia can be called once the pre-processing finishes.

      useEffect(() => {
        const loadItems = async () => {
          console.log("preprocessing here");
        };  
        loadItems().then(() => {
          setLoadedStatus(true);
        });
      }, []);
    
      useEffect(() => {
        if (!loaded) return;
        const getMedia = async () => {
          try {
            console.log(webcamRef.current);
            let stream = await navigator.mediaDevices.getUserMedia({ video: true });
            webcamRef.current.srcObject = stream;
            webcamRef.current.play();
          } catch (err) {
            console.error(err);
          }
        };
    
        getMedia();
      }, [loaded]);
    

    https://codesandbox.io/s/distracted-violet-s92d0?file=/src/App.js