TINTIN
TINTIN

Reputation: 183

ReactJs : useRef current getting null

I am trying to turnoff camera and flashlight when the component gets unmount , I am using react hook I have two function startCamera() and stopCamera() , I am calling startcamera when the component gets mount and stop camera when component gets unmount. But its showing me error when stopCamera is called while unmounting enter image description here

i have created an another button to test if StopCamera() working and i found its working , but i want to call the function when component is getting unmounted

my code:

CameraScreen.js


import "./styles.css";
import { useState, useEffect, useRef } from "react";

export default function CameraScreen() {
  const videoElement = useRef(null);
  const [facingMode, setFacingMode] = useState("environment");
  const handleFacingModeToggle = () => {
    stopCamera();
    facingMode === "environment"
      ? setFacingMode("user")
      : setFacingMode("environment");
  };
  useEffect(() => {
    // const getUserMedia = async () => {
    //   try {
    //     const stream = await navigator.mediaDevices.getUserMedia({
    //       video: true
    //     });
    //     videoElement.current.srcObject = stream;
    //   } catch (err) {
    //     console.log(err);
    //   }
    // };
    // getUserMedia();
    startCamera();
    return function cleanup() {
      stopCamera();
    };
  }, []);

  const stopCamera = () =>
    videoElement.current.srcObject &&
    videoElement.current.srcObject.getTracks().forEach((t) => t.stop());
  function startCamera() {
    if (navigator.mediaDevices.getUserMedia) {
      navigator.mediaDevices
        .getUserMedia({
          video: { facingMode: facingMode },
          width: { ideal: 1280 },
          height: { ideal: 720 }
        })
        .then(function (stream) {
          if (videoElement.current) videoElement.current.srcObject = stream;
          const track = stream.getVideoTracks()[0];

          //Create image capture object and get camera capabilities
          const imageCapture = new ImageCapture(track);
          const photoCapabilities = imageCapture
            .getPhotoCapabilities()
            .then(() => {
              //todo: check if camera has a torch

              //let there be light!

              track.applyConstraints({
                advanced: [{ torch: true }]
              });
            });
        })
        .catch(function (error) {
          alert("Please check your device permissions");
          console.log("Something went wrong!");
          console.log(error);
        });
      if (videoElement.current)
        videoElement.current.onloadeddata = function () {
          if (window.NativeDevice)
            window.NativeDevice.htmlCameraReadyToRecord(true);
        };
    }
  }
  return (
    <>
      <video
        autoPlay={true}
        ref={videoElement}
        style={{
          minHeight: "67.82vh",
          maxHeight: "67.82vh",
          maxWidth: "100%",
          minWidth: "100%"
        }}
        className="border-3rem bg-[#666]"
      ></video>

      <button onClick={stopCamera}> stopCamera</button>
    </>
  );
}

App.js

import "./styles.css";
import { useState } from "react";
import CameraScreen from "./cameraScreen";
export default function App() {
  const [switchS, setSwitchS] = useState(false);
  return (
    <div>
      <button className="" onClick={() => setSwitchS(!switchS)} value="switch">
        switch
      </button>
      
      {switchS && <CameraScreen />}
      {!switchS && "Blank Screen"}
    </div>
  );
}

PS: the above code working at :https://5t2to.csb.app/

codesandbox link : https://codesandbox.io/s/practical-fast-5t2to?file=/src/cameraScreen.js

Upvotes: 4

Views: 3721

Answers (4)

Aniruddh Thakor
Aniruddh Thakor

Reputation: 1616

Just need to change useEffect() to useLayoutEffect()and it will works like a charm.

useLayoutEffect(() => {
     const ref = videoElement;
     console.log(ref);
     startCamera();
  return function cleanup() {
     stopCamera();
  };
}, []);

sanbox link :- https://codesandbox.io/s/naughty-murdock-by5tc?file=/src/cameraScreen.js:415-731

Upvotes: 0

Harsh Kanjariya
Harsh Kanjariya

Reputation: 543

Simply add useLayoutEffect to stop camera

useEffect(() => {
    // const getUserMedia = async () => {
    //   try {
    //     const stream = await navigator.mediaDevices.getUserMedia({
    //       video: true
    //     });
    //     videoElement.current.srcObject = stream;
    //   } catch (err) {
    //     console.log(err);
    //   }
    // };
    // getUserMedia();
    startCamera();
  }, []);
useLayoutEffect(()=>()=>{
   stopCamera();
},[]);

Upvotes: 1

Drew Reese
Drew Reese

Reputation: 202605

It took a bit of debugging/sleuthing to find the issue. So even though you have a ref attached to the video element, when the component is unmounted the ref is still mutated and becomes undefined. The solution is to save a reference to the current videoElement ref value and use this in a cleanup function.

useEffect(() => {
  startCamera();

  const ref = videoElement.current;

  return () => {
    ref.srcObject.getTracks().forEach((t) => t.stop());
  };
}, []);

Edit reactjs-useref-current-getting-null

Upvotes: 1

Himanshu Singh
Himanshu Singh

Reputation: 1953

You can use useLayoutEffect hook. It works just before unmounting, like componentWillUnmount.

Here is an example to that https://codesandbox.io/s/happy-swartz-ikqdn?file=/src/random.js

You can go to https://ikqdn.csb.app/rand in sandbox browser and check the console on clicking to home button.

You can see the difference in working while unmounting of both useEffect and useLayoutEffect


It preserves ref.current, so what you can do is, you can pass ref.current in the function that you are calling just before unmounting, to prevent ref to the dom elment.

Upvotes: 4

Related Questions