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:
Thomas Hallock 2025-12-30 21:20:39 -06:00
parent db17c96168
commit 2a24700e6c
1 changed files with 15 additions and 3 deletions

View File

@ -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])