Reputation: 183
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
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
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
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
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());
};
}, []);
Upvotes: 1
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