when trying to capture the image its throwing an error, Too many re-renders
the function handleCapture is rendering too many times
const Survey: React.FC = () => {
const dispatch = useDispatch();
const navigate = useNavigate();
const [currentStep, setCurrentStep] = useState(0);
const [capturedPicture, setCapturePicture] = useState<string>('');
const [showPopup, setShowPopup] = useState(false);
const [popup, setPopup] = useState(false)
const [loading, setLoading] = useState(false)
const [enableGeolocation, setEnableGeolocation] = useState(false)
const [locationFetched, setLocationFetched] = useState(false)
const [isOnline, setIsOnline] = useState(navigator.onLine);
const currentDate = getCurrentTimeStampOfEndMarket()
const [selectDate, setselectDate] = useState(currentDate)
const employeeDetails = useSelector((state: RootState) => state.jwtToken.jwtTokenData?.UserData);
const imageCaptureLimit = 8;
const newOutlet = useSelector((state: RootState) => state.newOutletSurvey.data);
const surveyJson: SurveyDetail[] = newOutlet ? JSON.parse(newOutlet?.content!) : [];
const memoizedSurveyJson = useMemo(() => surveyJson, [surveyJson]);
const capturedImages = useSelector((state: RootState) =>
state?.photo.data.filter(image => image.source !== 'Survey') ?? []
);
const [surveyResponses, setSurveyResponses] = useState<LocalSurveyResponse[]>([]);
const [validationErrors, setValidationErrors] = useState<{ key: string; errorMessage: string; type: string; }[]>([]);
useEffect(() => {
return () => {
dispatch(clearImage())
setCapturePicture('');
}
}, []);
useEffect(() => {
if (capturedImages.length > 0) {
setCapturePicture(capturedImages[capturedImages.length - 1].images);
}
}, [capturedImages]);
const handleCapture = (imgSrc: string) => {
try {
const base64Image = imgSrc.replace(/^data:image/(png|jpeg);base64,/, '');
setCapturePicture(base64Image);
const data: PhotoModel = {
source: "newOutletSurvey",
images: base64Image
};
dispatch(addPhoto(data));
setShowPopup(false);
} catch (error) {
console.error("Error in capturing image", error);
}
}
const handleClick = () => {
setShowPopup(true);
};
const closePopup = () => {
setShowPopup(false);
};
const onRemoveClick = (index: number) => {
dispatch(deleteImage(index));
};
const updateSurveyResponse = (questionId: string, response: LocalSurveyResponse) => {
setSurveyResponses(prevState => {
const existingIndex = prevState.findIndex(item => item.questionId === questionId);
if (existingIndex !== -1) {
return prevState.map((item, i) => (i === existingIndex ? response : item));
} else {
return [...prevState, response];
}
});
};
const handleSelectedDate = (date: { day: string; month: string; year: string }) => {
const formattedDate = `${date.year}-${date.month}-${date.day}`;
setselectDate(formattedDate);
}
const setSteps = (surveyData: SurveyDetail[]) => {
const updatedSteps = surveyData.map((data, index) => {
const questionId = `${data.type}_${index}`;
switch (data.type) {
case 'text':
return (
<div key={questionId}>
<TextBox
label={data.Label}
placeholder={data.Placeholder}
onPageChange={(text) => {
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: [text],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
}}
/>
</div>
);
case 'checkbox':
return (
<div key={questionId}>
<Label Label={data.Label}>
<div className="flex flex-wrap gap-2 overflow-y-scroll no-scrollbar">
{data.options && data.options.map((option, optionIndex) => (
<div key={optionIndex}>
<ButtonWithText
isEnabled={surveyResponses.find(r => r.questionId === questionId)?.response.includes(option)}
// isSelected={surveyResponses.find(r => r.questionId === questionId)?.response.includes(option)}
label={option}
onClick={() => {
const currentResponse = surveyResponses.find(r => r.questionId === questionId)?.response || [];
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: currentResponse.includes(option)
? currentResponse.filter(item => item !== option)
: [...currentResponse, option],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
}}
type={'mini'}
/>
</div>
))}
</div>
</Label>
</div>
);
case 'radio':
return (
<div key={questionId}>
<Label Label={data.Label}>
<div className="flex flex-wrap gap-2 overflow-y-scroll no-scrollbar">
{data.options && data.options.map((option, optionIndex) => (
<div key={optionIndex}>
<SurveyRadioButton
isEnabled={surveyResponses.find(r => r.questionId === questionId)?.response.includes(option)}
label={option}
onClick={() => {
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: [option],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
}}
type={'mini'}
/>
</div>
))}
</div>
</Label>
</div>
);
case 'counter':
return (
<div key={questionId}>
<Counter
label={data.Label}
value={parseInt(surveyResponses.find(r => r.questionId === questionId)?.response[0] || '0')}
onCountChange={(newCount) => {
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: [newCount.toString()],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
}}
/>
</div>
);
case 'signature':
return (
<div key={questionId}>
<Label Label={data.Label}>
<SignatureCapture
onSave={(image) => {
const base64Image = image.replace(/^data:image/(png|jpeg);base64,/, '');
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: [base64Image],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
}}
/>
</Label>
</div>
);
case 'capture':
return (
<div className="w-full " key={questionId}>
<Label Label={t("Upload")}>
{capturedImages.length === 0 && (
<div
className="font-poppins-bold rounded-lg flex items-center justify-center w-full h-24"
onClick={() => handleClick()}
style={{ backgroundImage: "url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' rx='8' ry='8' stroke='black' stroke-width='2' stroke-dasharray='10' stroke-dashoffset='10' stroke-linecap='butt'/%3e%3c/svg%3e")" }}
>
<Icons.CAMERA_PLUS size={32} />
</div>
)}
<div className="w-full flex flex-wrap justify-space gap-3">
{capturedImages.length > 0 && capturedImages.length < imageCaptureLimit && (
<div
className="font-poppins-bold rounded-lg flex items-center justify-center bg-white w-24 h-24"
onClick={() => handleClick()}
>
<Icons.CAMERA_PLUS size={32} />
</div>
)}
{capturedImages.map((img: PhotoModel, i: number) => {
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: capturedImages.map(img => img.images),
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
return (
<ButtonWithImage
onRemoveClick={() => onRemoveClick(i)}
imageUrl={`data:image//(png|jpeg|jpg);base64,${img.images}`}
alt={`Image ${i}`}
enableCloseButton={true}
key={i}
/>
)
})}
</div>
</Label>
</div>
)
case 'date':
return (
<div key={questionId}>
<Label Label={data.Label}>
<CalendarPicker
dateFormat="dd-mm-yyyy"
initialValue={currentDate}
onDateChange={(date) => {
handleSelectedDate(date);
const formattedDate = `${date.year}-${date.month}-${date.day}`;
const newResponse: LocalSurveyResponse = {
questionId,
question: data.Label,
response: [formattedDate],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
}}
/>
</Label>
</div>
);
case 'locate me':
return (
<div key={questionId}>
<Label Label={data.Label}>
<div className='w-full'>
<ButtonWithIconAndLable
iconSrc={{ icon: icons.LOCATE_ME, size: 45, iconColor: '#DEB00F' } as IconProps}
label={t("Locate Me")}
onClick={async () => {
setLoading(true); // Set loading to true when the button is clicked
try {
const { latitude, longitude } = await GetCurrentLocation();
if (latitude && longitude) {
setLoading(false);
setLocationFetched(true);
setTimeout(() => {
setLocationFetched(false);
}, 1000);
const newResponse: LocalSurveyResponse = {
questionId,
question: '',
response: [`${latitude}, ${longitude}`],
type: data.type,
};
updateSurveyResponse(questionId, newResponse);
} else {
setLoading(false);
}
} catch (error) {
setLoading(false);
setPopup(true);
setEnableGeolocation(true);
console.error('Error processing:', error);
}
}}
/>
{loading ? (
<p className='font-bold text-lg text-center text-black'>Fetching location...</p>
) : (
locationFetched && (
<p className='font-bold text-lg text-center text-green-600'>Location fetched successfully</p>
)
)}
</div>
</Label>
</div>
); default:
return null;
}
});
return updatedSteps
};
const steps = useMemo(() => {
return setSteps(surveyJson);
}, [surveyJson]);
const handleNextClick = async () => {
if (!newOutlet) {
navigate('/home');
} else {
const currentSurveyItem = surveyJson[currentStep];
const questionId = `${currentSurveyItem.type}_${currentStep}`;
const currentResponse = surveyResponses.find(r => r.questionId === questionId);
let isValid = true;
let errorMessage = '';
switch (currentSurveyItem.type) {
case 'text':
if (!currentResponse?.response[0]?.trim()) {
isValid = false;
errorMessage = 'The field should not be empty.';
}
break;
case 'checkbox':
if (!currentResponse?.response.length) {
isValid = false;
errorMessage = 'Please select at least one option.';
}
break;
case 'counter':
if (!currentResponse?.response[0] || parseInt(currentResponse.response[0]) <= 0) {
isValid = false;
errorMessage = 'Count should be greater than zero';
}
break;
case 'upload':
if (!capturedImages.length) {
isValid = false;
errorMessage = 'Please capture/upload a picture.';
}
break;
case 'radio':
if (!currentResponse?.response.length) {
isValid = false;
errorMessage = 'Please select an option.';
}
break;
case 'signature':
if (!currentResponse?.response.length) {
isValid = false;
errorMessage = 'Please provide a signature.';
}
break;
case 'date':
if (!currentResponse?.response[0]) {
isValid = false;
errorMessage = 'Please select a date.';
}
break;
case 'locate me':
if (!currentResponse?.response[0]) {
isValid = false;
errorMessage = 'Please locate your position.';
}
break;
}
if (!isValid) {
setValidationErrors(prevErrors => [
...prevErrors.filter(e => e.key !== questionId),
{ key: questionId, errorMessage, type: currentSurveyItem.type }
]);
return;
}
setValidationErrors(prevErrors => prevErrors.filter(e => e.key !== questionId));
if (currentStep < setSteps(surveyJson).length - 1) {
setCurrentStep(currentStep + 1);
dispatch(clearImage())
setCapturePicture('');
} else {
const submissionResponses: GeneratedSurveyResponse[] = surveyResponses.map(({ question, response, type }) => ({
question,
response,
type
}));
const requestData: NewOutletSurveyResponseCreateRequest = {
surveyId: newOutlet?.id,
surveyData: submissionResponses,
employeeEmail: employeeDetails?.Email,
timeStamp: getCurrentTimeStampOfEndMarket(),
noOfQuestions: memoizedSurveyJson.length,
noOfAnswered: surveyResponses.length,
};
isOnline ? dispatch(createNewOutlet(requestData)) : dispatch(setNewOutLetSurvey(requestData))
dispatch(clearImage());
setCapturePicture('');
dispatch(addRoute(RouterName.SucessfullPage));
navigate(RouterName.SucessfullPage);
}
}
};
const handlePreviousClick = () => {
if (currentStep > 0) {
const currentQuestionId = `${surveyJson[currentStep].type}_${currentStep}`;
setValidationErrors(prevErrors => prevErrors.filter(e => e.key !== currentQuestionId));
setCurrentStep(currentStep - 1);
}
};
console.log('surveyJson', surveyJson)
return (
<div className="relative h-screen">
{!showPopup && <NavigateHeader />}
{showPopup ? (
<CameraPopup onCapture={handleCapture} onClose={closePopup} />
) : (
<>
<Body>
{!newOutlet ? (
<div className="flex flex-col items-center justify-center h-full">
<Icons.WARNING size={100} />
<h1 className="text-2xl font-bold text-heading items-center mx-5">No survey data available.!</h1>
</div>
) : (
<>
<div>{steps[currentStep]}</div>
{validationErrors.length > 0 && (
<div className="validation-errors">
{validationErrors.map((error, index) => (
<span key={index} className={`${GetStatusColor('pending')}`}>
{error.errorMessage}
</span>
))}
</div>
)}
</>
)}
</Body>
<div className="absolute bottom-4 right-4">
<div className="flex justify-between items-center gap-3">
{newOutlet && currentStep > 0 && (
<ButtonWithIcon
alt="Previous"
color={currentStep === 0 ? 'bg-grey' : 'bg-Ash'}
height_and_width="h-20 w-20"
icon={icons.LEFT_ARROW}
size={36}
onClick={handlePreviousClick}
/>
)}
<ButtonWithIcon
alt="Next"
color="bg-Ash"
height_and_width="h-20 w-20"
icon={icons.RIGHT_ARROW}
size={36}
onClick={handleNextClick}
/>
</div>
</div>
</>
)}
{enableGeolocation && (
<WarningPopUp
message={"Enable Geolocation"}
showPopup={popup}
onClose={() => {
setPopup(false)
setEnableGeolocation(false)
}}
/>
)}
</div>
);
};
export default Survey;
when trying to capture an image the handleCapture function is throwing an error
import React, { useRef, useState } from 'react';
import { FiCamera, FiRefreshCw } from 'react-icons/fi';
import { AiOutlineFullscreenExit } from 'react-icons/ai';
import Webcam from 'react-webcam';
interface CameraPopupProps {
onClose: () => void;
onCapture?: (imageBase62: string) => void;
}
const CameraPopup: React.FC<CameraPopupProps> = ({ onClose , onCapture }) => {
const webcamRef = useRef<Webcam | null>(null);
const [facingMode, setFacingMode] = useState('environment');
const capture = () => {
if (webcamRef.current) {
const newImageSrc = webcamRef.current.getScreenshot();
if (newImageSrc) {
onCapture && onCapture(newImageSrc); // Pass newImageSrc to onCapture callback
onClose();
} else {
console.error("Failed to capture image."); // Log error if image capture fails
}
} else {
console.error("Webcam reference is not available."); // Log error if webcam reference is not available
}
};
const switchCamera = () => {
setFacingMode(prevMode => (prevMode === 'user' ? 'environment' : 'user'));
};
const isMirrored = facingMode === 'user';
return (
<div className="z-index popup z-50">
<div className="popup-inner">
<div className="w-[dvw] h-[dvh] bg-Ash relative">
<Webcam
mirrored={isMirrored}
audio={false}
ref={webcamRef}
screenshotFormat="image/jpeg"
videoConstraints={{ facingMode }}
style={{ width: '100dvh', height: '100dvh', objectFit: 'contain' }}
/>
<div className="flex justify-around">
<button onClick={capture} className="absolute bottom-4 right-auto bg-white p-2 rounded-full">
<FiCamera size={24} color="#000" />
</button>
<button onClick={switchCamera} className="absolute bottom-4 left-4 bg-white p-2 rounded-full">
<FiRefreshCw size={24} color="#000" />
</button>
<button onClick={onClose} className="absolute bottom-4 right-4 bg-white p-2 rounded-full">
<AiOutlineFullscreenExit size={24} color="#000" />
</button>
</div>
</div>
</div>
</div>
);
};
export default CameraPopup;
how can i fix this issue
i have tried to use callback, but becouse the imgSrc on handleCapture is comming from capture function in CameraPopup its throwing an error
<CameraPopup onCapture={()=>handleCapture()} onClose={closePopup} />