1
votes

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

1
The problem with the Chrome console is it doesn't reflect the state of objects at the time you log; it's when the console UI evaluates the object that you'll see the output. So you logged at the time when the ref was null, but then by the time the UI updates in Chrome it's populated.Jacob

1 Answers

0
votes

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