fix(camera): handle race condition in camera initialization
Add cancelled flag to prevent setting state after unmount and properly cleanup streams that were acquired after component unmounted (e.g. in React Strict Mode double-mounting). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
db17c96168
commit
2a24700e6c
|
|
@ -376,10 +376,10 @@ function FullscreenCamera({ onCapture, onClose, disabled = false }: FullscreenCa
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (disabled) return
|
if (disabled) return
|
||||||
|
|
||||||
|
let cancelled = false
|
||||||
|
|
||||||
const startCamera = async () => {
|
const startCamera = async () => {
|
||||||
try {
|
try {
|
||||||
setError(null)
|
|
||||||
|
|
||||||
// Request camera with rear camera preference on mobile
|
// Request camera with rear camera preference on mobile
|
||||||
const constraints: MediaStreamConstraints = {
|
const constraints: MediaStreamConstraints = {
|
||||||
video: {
|
video: {
|
||||||
|
|
@ -391,14 +391,24 @@ function FullscreenCamera({ onCapture, onClose, disabled = false }: FullscreenCa
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = await navigator.mediaDevices.getUserMedia(constraints)
|
const stream = await navigator.mediaDevices.getUserMedia(constraints)
|
||||||
|
|
||||||
|
// If component unmounted while waiting for camera, stop the stream
|
||||||
|
if (cancelled) {
|
||||||
|
stream.getTracks().forEach((track) => track.stop())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
streamRef.current = stream
|
streamRef.current = stream
|
||||||
|
|
||||||
if (videoRef.current) {
|
if (videoRef.current) {
|
||||||
videoRef.current.srcObject = stream
|
videoRef.current.srcObject = stream
|
||||||
await videoRef.current.play()
|
await videoRef.current.play()
|
||||||
setIsReady(true)
|
if (!cancelled) {
|
||||||
|
setIsReady(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (cancelled) return
|
||||||
console.error('Camera access error:', err)
|
console.error('Camera access error:', err)
|
||||||
setError('Camera access denied. Please allow camera access and try again.')
|
setError('Camera access denied. Please allow camera access and try again.')
|
||||||
}
|
}
|
||||||
|
|
@ -407,8 +417,10 @@ function FullscreenCamera({ onCapture, onClose, disabled = false }: FullscreenCa
|
||||||
startCamera()
|
startCamera()
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
cancelled = true
|
||||||
if (streamRef.current) {
|
if (streamRef.current) {
|
||||||
streamRef.current.getTracks().forEach((track) => track.stop())
|
streamRef.current.getTracks().forEach((track) => track.stop())
|
||||||
|
streamRef.current = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [disabled])
|
}, [disabled])
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue