Fix: Implement isCancelled pattern and memoize callbacks to prevent camera unmounting

- Wrap all AvatarSelector callback handlers in useCallback in CreateModal.tsx
- Add isCancelled flag pattern to RobustCamera useEffect
- Inline camera initialization to avoid stale closure issues
- Add proper cleanup on component unmount
- Ensure camera stream is properly stopped if component unmounts during initialization
- Remove unused initializeCamera function
This commit is contained in:
ajaysi
2026-04-07 11:39:07 +05:30
parent e66311ea44
commit 80838ed028
2 changed files with 158 additions and 121 deletions

View File

@@ -25,9 +25,10 @@ interface CreateModalProps {
open: boolean;
defaultKnobs: Knobs;
isSubmitting?: boolean;
announcement?: string;
}
export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaultKnobs, isSubmitting = false }) => {
export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaultKnobs, isSubmitting = false, announcement }) => {
const { subscription } = useSubscription();
const [topicInput, setTopicInput] = useState("");
const [showAIDetailsButton, setShowAIDetailsButton] = useState(false);
@@ -365,7 +366,7 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
setAvatarUrl(url);
}, []);
const handleAvatarChange = async (e: React.ChangeEvent<HTMLInputElement>) => {
const handleAvatarChange = React.useCallback(async (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {
// Validate file type
@@ -396,9 +397,9 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
// Continue with local preview - upload will happen on submit
}
}
};
}, []);
const handleCameraSelfie = async (imageDataUrl: string) => {
const handleCameraSelfie = React.useCallback(async (imageDataUrl: string) => {
try {
// Convert dataURL to File object
const response = await fetch(imageDataUrl);
@@ -424,9 +425,9 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
} catch (error) {
console.error('Failed to process selfie:', error);
}
};
}, []);
const handleRemoveAvatar = () => {
const handleRemoveAvatar = React.useCallback(() => {
setAvatarFile(null);
setAvatarPreview(null);
setAvatarUrl(null);
@@ -435,9 +436,9 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
}
setAvatarPreviewBlobUrl(null);
setMakingPresentable(false);
};
}, [avatarPreviewBlobUrl]);
const handleUseBrandAvatar = async () => {
const handleUseBrandAvatar = React.useCallback(async () => {
if (brandAvatarFromDb) {
setAvatarFile(null);
setAvatarPreview(brandAvatarFromDb);
@@ -466,7 +467,7 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
} finally {
setLoadingBrandAvatar(false);
}
};
}, [brandAvatarFromDb, brandAvatarBlobUrl, loadingBrandAvatar]);
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
setAvatarTab(newValue);
@@ -492,7 +493,7 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const handleMakePresentable = async () => {
const handleMakePresentable = React.useCallback(async () => {
if (!avatarUrl || makingPresentable) return;
try {
@@ -512,7 +513,7 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
} finally {
setMakingPresentable(false);
}
};
}, [avatarUrl, makingPresentable]);
return (
<Paper
@@ -586,6 +587,7 @@ export const CreateModal: React.FC<CreateModalProps> = ({ onCreate, open, defaul
submit={submit}
canSubmit={canSubmit}
isSubmitting={isSubmitting}
announcement={announcement}
/>
{/* Enhanced Topic Choices Modal */}