I have a problem, I have a form of multiple steps. Between each step there is a Continue
and Back
button to transition back and forth between steps. However, for the Continue
button, it requires double click in order to move forward, given the condition that all required fields are completed.
Step1.txs
interface Step1Props {
onValidationChange: (isValid: boolean) => void;
setErrorMessage: (message: string) => void;
hasAttempted1Continue: boolean;
}
interface FormData {
location?: string;
title?: string;
category?: string;
}
const Step1: React.FC<Step1Props> = ({
onValidationChange,
setErrorMessage,
hasAttempted1Continue,
}) => {
const [formData, setFormData] = useState<FormData>({});
const [errors, setErrors] = useState<{ [key: string]: string }>({});
useEffect(() => {
if (hasAttempted1Continue) {
const newErrors: { [key: string]: string } = {};
if (!formData.title) newErrors.title = "Campaign title is required.";
if (!formData.location) newErrors.location = "Location is required.";
if (!formData.category) newErrors.category = "Category is required.";
setErrors(newErrors);
const isValid = Object.keys(newErrors).length === 0;
onValidationChange(isValid);
if (!isValid) {
setErrorMessage(
// "Please fix the following errors: " +
Object.values(newErrors).join(", "),
);
} else {
setErrorMessage("");
}
}
}, [
formData,
onValidationChange,
setErrorMessage,
hasAttempted1Continue,
]);
return (
<div className="flex flex-col gap-y-[30px] pb-[10px] pt-[10px]">
<CardButtonInput
placeholder={t("title")}
type="text"
name="title"
value={formData.title}
onChange={handleInputChange}
error={errors["title"]}
/>
<Radio
options={cities}
name="location"
placeholder={t("location")}
value={formData.location}
onChange={handleLocationChange}
error={errors["location"]}
float={true}
/>
<Radio
options={campaignCategory}
name="category"
placeholder={"Category"}
value={formData.category}
onChange={handleCategoryChange}
error={errors["cateory"]}
float={true}
/>
</div>
)
Main.tsx
Const Main = () => {
const [currentStep, setCurrentStep] = useState(1);
const [isStep1Valid, setIsStep1Valid] = useState(false);
const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [hasAttempted1Continue, sethasAttempted1Continue] = useState(false);
const handleContinue = async () => {
if (currentStep === 1) {
sethasAttempted1Continue(true);
if (!isStep1Valid) {
setErrorMessage("Please complete the information before continuing..");
return;
}
if (currentStep === 2) {
sethasAttempted2Continue(true);
if (!isStep2Valid) {
setErrorMessage("Please complete the information before continuing..");
return;
}
try {
//CallAPI() + payload
} catch (error) {
setErrorMessage("Failed to submit the form. Please try again.");
return;
}
} else {
setCurrentStep((prevStep) =>
Math.min(prevStep + 1, StepItems.length + 1),
);
window.scrollTo({
top: 0,
behavior: "smooth",
});
}
setErrorMessage(null);
}
const getStepItems = () => {
const commonSteps = [
{
statusDefault: "default",
statusVerified: "verified",
statusUnverified: "unverified",
personalInformation: t("step1"),
numberOfSteps: 1,
},
{
statusDefault: "default",
statusVerified: "verified",
statusUnverified: "unverified",
personalInformation: t("step2"),
numberOfSteps: 2,
},
...
];
return commonSteps.map((step, index) => {
if (index + 1 < currentStep) {
return { ...step, status: "verified" };
} else if (index + 1 === currentStep) {
return { ...step, status: "unverified" };
}
return { ...step, status: "" };
});
};
...
return (
<div className="flex flex-row items-center justify-center">
{StepItems.map((card, index) => (
<React.Fragment key={index}>
<CardStep
status={card.status}
statusDefault={card.statusDefault}
statusVerified={card.statusVerified}
statusUnverified={card.statusUnverified}
personalInformation={card.personalInformation}
numberOfSteps={card.numberOfSteps}
/>
{index < StepItems.length - 1 && (
<div className="mb-20 flex flex-row items-center justify-center">
<div className="h-[5px] w-[5px] rounded-full"></div>
<div className="w-[150px] border-t border-[#9F76FF]"></div>
<div className="h-[5px] w-[5px] rounded-full"></div>
</div>
)}
</React.Fragment>
))}
</div>
{currentStep === 1 && (
<Step1
onValidationChange={setIsStep1Valid}
setErrorMessage={setErrorMessage}
hasAttempted1Continue={hasAttempted1Continue}
/>
)}
{currentStep === 2 && (
<Step2
onValidationChange={setIsStep2Valid}
setErrorMessage={setErrorMessage}
hasAttempted2Continue={hasAttempted2Continue}
/>
)}
{currentStep === 3 && <Step3 />}
{currentStep === 4 && <Step4 />}
<div className="flex flex-row items-center justify-center gap-x-[20px]">
<CardButton
text={t("back")}
border="border-[#9F76FF]"
color="text-[#9F76FF]"
bg="bg-transparent"
onClick={handleBack}
/>
<CardButton
text={t("continue")}
onClick={handleContinue}
/>
</div>
)
}
After some time of debugging, I have found that in handleContinue()
of Main.tsx
, the return statement causes the logic. Meaning, if i commented out the line, clicking Continue shall allow me to move to step 2 even when errors are detected in the form.
I do know that return
are meant to disrupt the process of moving to step 2 when there are errors detected. But it also blocks me from moving to next step even when all required data are filled.
I want, when all required fields are completed, clicking once on Continue
button shall take the client to the next step. How can I resolve this?