我有一个问题,我有一个包含多个步骤的表单。每个步骤之间都有一个
Continue
和 Back
按钮,用于在步骤之间来回转换。但是,对于 Continue
按钮,在所有必填字段均已填写的情况下,需要双击才能向前移动。
步骤1.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>
)
主要.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>
)
}
经过一段时间的调试,我发现在
handleContinue()
的Main.tsx
中,return语句导致了逻辑。这意味着,如果我注释掉该行,即使在表单中检测到错误,单击“继续”也将允许我转到步骤 2。
我确实知道
return
的目的是在检测到错误时中断进入步骤 2 的过程。但即使所有必需的数据都已填写,它也会阻止我进入下一步。
我希望,当所有必填字段完成后,单击
Continue
按钮将使客户进入下一步。我该如何解决这个问题?
你所做的事情并不真实。这就是我的做法。
import React, { ReactElement, useState, useMemo, useCallback } from 'react';
export function useMultiStepForm<C>(
steps: { component: ReactElement; label: string }[],
defaultData?: Partial<C>
) {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const [stepsData, setStepsData] = useState<Partial<C> | undefined>(
defaultData
);
const next = useCallback(() => {
setCurrentStepIndex((i) => {
if (i >= steps.length - 1) return i;
return i + 1;
});
}, [steps.length]);
function back() {
setCurrentStepIndex((i) => {
if (i <= 0) return i;
return i - 1;
});
}
function goTo(index: number) {
setCurrentStepIndex(index);
}
const stepComponents = useMemo(
() =>
steps.map((stepComponent, index) => {
return React.cloneElement(stepComponent.component, {
key: index,
onNext: next,
stepsData,
isActive: currentStepIndex === index,
setStepsData,
});
}),
[steps, next, currentStepIndex, stepsData]
);
return {
currentStepIndex,
step: stepComponents[currentStepIndex],
steps: stepComponents,
originalSteps: steps,
isFirstStep: currentStepIndex === 0,
isLastStep: currentStepIndex === steps.length - 1,
goTo,
next,
back,
};
}
成分:
import React from 'react';
import AdditionalInfo from '@/components/ui/PropertyForm/Form/AdditionalInfo';
import BasicInfo from '@/components/ui/PropertyForm/Form/BasicInfo';
import Images from '@/components/ui/PropertyForm/Form/Images';
import LocationInfo from '@/components/ui/PropertyForm/Form/LocationInfo';
import { useMultiStepForm } from '@/hooks/useMultiStepForm';
import { PropertyType } from '@/types';
import StepNavigation from './StepNavigation';
import Videos from './Videos';
import VisitingTimeInfo from './VisitingTimeInfo';
const placeHolderFn = () => {};
export default function Form({
defaultValues,
}: {
defaultValues?: PropertyType;
}) {
const { currentStepIndex, step, goTo, originalSteps } = useMultiStepForm<any>(
[
{
component: (
<BasicInfo
stepsData={{}}
onNext={placeHolderFn}
setStepsData={placeHolderFn}
key={'basic-info'}
defaultValues={defaultValues}
/>
),
label: 'Basic Info',
},
//NOTE landlord is not allowed to edit location
...(defaultValues
? []
: [
{
component: (
<LocationInfo
stepsData={{}}
key={'location'}
onNext={placeHolderFn}
setStepsData={placeHolderFn}
defaultValues={defaultValues}
/>
),
label: 'Location',
},
]),
{
component: (
<AdditionalInfo
stepsData={{}}
key={'availability-and-amenities'}
onNext={placeHolderFn}
setStepsData={placeHolderFn}
defaultValues={defaultValues}
/>
),
label: 'Additional Info',
},
{
component: (
<VisitingTimeInfo
stepsData={{}}
key={'visiting-times'}
onNext={placeHolderFn}
setStepsData={placeHolderFn}
defaultValues={defaultValues}
/>
),
label: 'VISIT TIMES',
},
{
component: (
<Images
stepsData={{}}
onNext={placeHolderFn}
setStepsData={placeHolderFn}
key={'images'}
defaultValues={defaultValues}
/>
),
label: 'Images',
},
{
component: (
<Videos
stepsData={{}}
onNext={placeHolderFn}
setStepsData={placeHolderFn}
key={'videos'}
defaultValues={defaultValues}
/>
),
label: 'Videos',
},
]
);
return (
<div>
<StepNavigation
allowNavigationOnClick={!!defaultValues}
currentStepIndex={currentStepIndex}
steps={originalSteps}
goTo={goTo}
isEditing={!!defaultValues}
/>
<div>{step}</div>
</div>
);
}