refactor(steps): simplify step presenters by removing unnecessary useImperativeHandle calls

- Removed useImperativeHandle and getValues from BenefitsStep, ChallengesStep, ChartStep, ContactsStep, FeaturesStep, FooterStep, and HeroStep components.
- Updated step presenters to directly use refs for improved data handling and validation.
- Enhanced code readability and maintainability by streamlining the presenter functions.
This commit is contained in:
AmirReza Jamali
2026-04-18 12:41:48 +03:30
parent c1b6b4ccf1
commit 69e75eb9b8
16 changed files with 364 additions and 272 deletions
@@ -1,5 +1,5 @@
"use client";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import SectionStep from "./SectionStep";
import useBenefitsStepPresenter, {
IBenefitsStepFields,
@@ -19,12 +19,7 @@ const BenefitsStep = forwardRef<unknown, BenefitsStepProps>(({ initialData }, re
append,
remove,
control,
getValues,
} = useBenefitsStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
} = useBenefitsStepPresenter(initialData, ref);
return (
<SectionStep<IBenefitsStepFields>
@@ -1,5 +1,5 @@
"use client";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import SectionStep from "./SectionStep";
import useChallengesStepPresenter, {
IChallengesStepFields,
@@ -20,12 +20,7 @@ const ChallengesStep = forwardRef<unknown, ChallengesStepProps>(
append,
remove,
control,
getValues,
} = useChallengesStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
} = useChallengesStepPresenter(initialData, ref);
return (
<SectionStep<IChallengesStepFields>
+2 -7
View File
@@ -4,7 +4,7 @@ import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { TrashIcon } from "@/assets/icons";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import { ChartPreview } from "./ChartPreview";
import useChartStepPresenter from "./useChartStepPresenter";
@@ -23,12 +23,7 @@ const ChartStep = forwardRef<unknown, ChartStepProps>(
append,
remove,
watch,
getValues,
} = useChartStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
} = useChartStepPresenter(initialData, ref);
const chartTitle = watch("title");
const chartData = watch("data");
@@ -4,7 +4,7 @@ import InputGroup from "@/components/FormElements/InputGroup";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import useContactsStepPresenter from "./useContactsStepPresenter";
interface ContactsStepProps {
@@ -13,12 +13,8 @@ interface ContactsStepProps {
const ContactsStep = forwardRef<unknown, ContactsStepProps>(
({ initialData }, ref) => {
const { errors, handleSubmit, onSubmit, register, fields, getValues } =
useContactsStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const { errors, handleSubmit, onSubmit, register, fields } =
useContactsStepPresenter(initialData, ref);
return (
<form
@@ -1,5 +1,5 @@
"use client";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import SectionStep from "./SectionStep";
import useFeaturesStepPresenter, {
IFeaturesStepFields,
@@ -20,12 +20,7 @@ const FeaturesStep = forwardRef<unknown, FeaturesStepProps>(
append,
remove,
control,
getValues,
} = useFeaturesStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
} = useFeaturesStepPresenter(initialData, ref);
return (
<SectionStep<IFeaturesStepFields>
+5 -7
View File
@@ -3,7 +3,7 @@ import InputGroup from "@/components/FormElements/InputGroup";
import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import useFooterStepPresenter from "./useFooterStepPresenter";
interface FooterStepProps {
@@ -12,12 +12,10 @@ interface FooterStepProps {
const FooterStep = forwardRef<unknown, FooterStepProps>(
({ initialData }, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } =
useFooterStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const { errors, handleSubmit, onSubmit, register } = useFooterStepPresenter(
initialData,
ref,
);
return (
<form
+5 -7
View File
@@ -4,7 +4,7 @@ import { TextAreaGroup } from "@/components/FormElements/InputGroup/text-area";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import { REGEX } from "@/constants/regex";
import { FormErrorMessages } from "@/constants/Texts";
import { forwardRef, useImperativeHandle } from "react";
import { forwardRef } from "react";
import useHeroStepPresenter from "./useHeroStepPresenter";
interface HeroStepProps {
@@ -12,12 +12,10 @@ interface HeroStepProps {
}
const HeroStep = forwardRef<unknown, HeroStepProps>(({ initialData }, ref) => {
const { errors, handleSubmit, onSubmit, register, getValues } =
useHeroStepPresenter(initialData);
useImperativeHandle(ref, () => ({
getData: () => getValues(),
}));
const { errors, handleSubmit, onSubmit, register } = useHeroStepPresenter(
initialData,
ref,
);
return (
<form
+11 -178
View File
@@ -4,13 +4,11 @@ import Breadcrumb from "@/components/Breadcrumbs/Breadcrumb";
import InputGroup from "@/components/FormElements/InputGroup";
import { Select } from "@/components/FormElements/select";
import { ShowcaseSection } from "@/components/Layouts/showcase-section";
import Stepper, { Step } from "@/components/ui/Stepper";
import Stepper from "@/components/ui/Stepper";
import { Countries } from "@/constants/countries";
import { Languages } from "@/constants/languages";
import { FormErrorMessages } from "@/constants/Texts";
import { BreadcrumbItem } from "@/types/shared";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
import BenefitsStep from "./BenefitsStep";
import ChallengesStep from "./ChallengesStep";
import ChartStep from "./ChartStep";
@@ -18,14 +16,7 @@ import ContactsStep from "./ContactsStep";
import FeaturesStep from "./FeaturesStep";
import FooterStep from "./FooterStep";
import HeroStep from "./HeroStep";
interface IPageTypeFields {
type: "company" | "organization";
language?: string;
company_name?: string;
country?: string;
key: string;
}
import useMarketingForm from "./useMarketingForm";
interface MarketingFormProps {
breadcrumbItems: BreadcrumbItem[];
@@ -42,48 +33,16 @@ const MarketingForm = ({
isPending,
initialData,
}: MarketingFormProps) => {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState<any>(initialData || {});
const heroRef = useRef<any>(null);
const chartRef = useRef<any>(null);
const featuresRef = useRef<any>(null);
const challengesRef = useRef<any>(null);
const benefitsRef = useRef<any>(null);
const contactsRef = useRef<any>(null);
const footerRef = useRef<any>(null);
const {
register: registerPageType,
watch: watchPageType,
formState: { errors: pageTypeErrors },
getValues: getPageTypeValues,
step,
formData,
steps,
handleStepChange,
handleSubmitAll,
registerPageType,
pageTypeErrors,
isCompany,
setValue,
reset,
} = useForm<IPageTypeFields>({
defaultValues: {
type: "company",
},
});
useEffect(() => {
if (initialData) {
const formValues = {
type: initialData.type || "company",
language: initialData.language || "",
company_name: initialData.company_name || "",
country: initialData.country || "",
key: initialData.key || "",
};
reset(formValues);
setFormData(initialData);
}
}, [initialData, reset]);
const pageType = watchPageType("type");
const isCompany = pageType === "company";
const saveCurrentStepData = async () => {
const refs = [
heroRef,
chartRef,
featuresRef,
@@ -91,133 +50,7 @@ const MarketingForm = ({
benefitsRef,
contactsRef,
footerRef,
];
const keys = [
"hero",
"chart",
"features",
"challenges",
"advantages",
"contact",
"footer",
];
const currentRef = refs[step];
if (currentRef?.current?.getData) {
try {
const data = await currentRef.current.getData();
setFormData((prev: any) => ({
...prev,
[keys[step]]: data,
}));
} catch (error) {
console.error(`Error collecting data from step ${step}:`, error);
}
}
};
const collectAllStepsData = async () => {
await saveCurrentStepData();
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const allData: any = { ...formData };
const keys = [
"hero",
"chart",
"features",
"challenges",
"advantages",
"contact",
"footer",
];
for (let i = 0; i < refs.length; i++) {
const ref = refs[i];
if (ref?.current?.getData) {
try {
const data = await ref.current.getData();
allData[keys[i]] = data;
} catch (error) {
console.error(`Error collecting data from step ${i}:`, error);
}
}
}
return allData;
};
const handleSubmitAll = async () => {
const allData = await collectAllStepsData();
const pageTypeData = getPageTypeValues();
const finalData = {
...allData,
type: pageTypeData.type,
key: pageTypeData.key,
...(pageTypeData.type === "company" && {
language: pageTypeData.language,
company_name: pageTypeData.company_name,
country: pageTypeData.country,
}),
};
onSubmit(finalData);
};
const steps: Step[] = [
{
title: "Hero",
description: "Create the hero section",
content: <></>,
},
{
title: "Chart",
description: "Create the chart",
content: <></>,
},
{
title: "Features",
description: "Add features",
content: <></>,
},
{
title: "Challenges",
description: "Create challenges section",
content: <></>,
},
{
title: "Benefits",
description: "create benefits section",
content: <></>,
},
{
title: "Contacts",
description: "Create contacts section",
content: <></>,
},
{
title: "Footer",
description: "Add footer information",
content: <></>,
},
];
const handleStepChange = async (newStep: number) => {
await saveCurrentStepData();
setStep(newStep);
};
} = useMarketingForm(initialData, onSubmit);
return (
<>
<Breadcrumb items={breadcrumbItems} />
@@ -1,4 +1,5 @@
"use client";
import { ForwardedRef, useImperativeHandle } from "react";
import { useFieldArray, useForm } from "react-hook-form";
export interface IBenefitsStepFields {
@@ -11,7 +12,10 @@ export interface IBenefitsStepFields {
}[];
}
const useBenefitsStepPresenter = (initialData?: IBenefitsStepFields) => {
const useBenefitsStepPresenter = (
initialData?: IBenefitsStepFields,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
@@ -19,6 +23,7 @@ const useBenefitsStepPresenter = (initialData?: IBenefitsStepFields) => {
watch,
formState: { errors },
getValues,
trigger,
} = useForm<IBenefitsStepFields>({
defaultValues: initialData || {
cards: [{ icon: "", title: "", description: "" }],
@@ -34,9 +39,10 @@ const useBenefitsStepPresenter = (initialData?: IBenefitsStepFields) => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
@@ -48,8 +54,6 @@ const useBenefitsStepPresenter = (initialData?: IBenefitsStepFields) => {
remove,
control,
watch,
getValues,
collectData,
};
};
@@ -1,4 +1,5 @@
"use client";
import { ForwardedRef, useImperativeHandle } from "react";
import { useFieldArray, useForm } from "react-hook-form";
export interface IChallengesStepFields {
@@ -11,7 +12,10 @@ export interface IChallengesStepFields {
}[];
}
const useChallengesStepPresenter = (initialData?: IChallengesStepFields) => {
const useChallengesStepPresenter = (
initialData?: IChallengesStepFields,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
@@ -19,6 +23,7 @@ const useChallengesStepPresenter = (initialData?: IChallengesStepFields) => {
watch,
formState: { errors },
getValues,
trigger,
} = useForm<IChallengesStepFields>({
defaultValues: initialData || {
cards: [{ icon: "", title: "", description: "" }],
@@ -34,9 +39,10 @@ const useChallengesStepPresenter = (initialData?: IChallengesStepFields) => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
@@ -48,8 +54,6 @@ const useChallengesStepPresenter = (initialData?: IChallengesStepFields) => {
remove,
control,
watch,
getValues,
collectData,
};
};
@@ -1,4 +1,5 @@
"use client";
import { ForwardedRef, useImperativeHandle } from "react";
import { useFieldArray, useForm } from "react-hook-form";
export interface IChartStepFields {
@@ -7,7 +8,10 @@ export interface IChartStepFields {
data: { key: string; value: number }[];
}
const useChartStepPresenter = (initialData?: IChartStepFields) => {
const useChartStepPresenter = (
initialData?: IChartStepFields,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
@@ -15,6 +19,7 @@ const useChartStepPresenter = (initialData?: IChartStepFields) => {
watch,
formState: { errors },
getValues,
trigger,
} = useForm<IChartStepFields>({
defaultValues: initialData || {
data: [{ key: "", value: 0 }],
@@ -30,9 +35,10 @@ const useChartStepPresenter = (initialData?: IChartStepFields) => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
@@ -44,8 +50,6 @@ const useChartStepPresenter = (initialData?: IChartStepFields) => {
remove,
control,
watch,
getValues,
collectData,
};
};
@@ -1,4 +1,5 @@
"use client";
import { ForwardedRef, useImperativeHandle } from "react";
import { useFieldArray, useForm } from "react-hook-form";
export interface IContactsStepFields {
@@ -13,13 +14,17 @@ export interface IContactsStepFields {
}[];
}
const useContactsStepPresenter = (initialData?: IContactsStepFields) => {
const useContactsStepPresenter = (
initialData?: IContactsStepFields,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
control,
getValues,
formState: { errors },
trigger,
} = useForm<IContactsStepFields>({
defaultValues: initialData || {
fields: [
@@ -50,9 +55,10 @@ const useContactsStepPresenter = (initialData?: IContactsStepFields) => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
@@ -61,8 +67,6 @@ const useContactsStepPresenter = (initialData?: IContactsStepFields) => {
onSubmit,
fields,
control,
collectData,
getValues,
};
};
@@ -1,4 +1,5 @@
"use client";
import { ForwardedRef, useImperativeHandle } from "react";
import { useFieldArray, useForm } from "react-hook-form";
export interface IFeaturesStepFields {
@@ -11,7 +12,10 @@ export interface IFeaturesStepFields {
}[];
}
const useFeaturesStepPresenter = (initialData?: IFeaturesStepFields) => {
const useFeaturesStepPresenter = (
initialData?: IFeaturesStepFields,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
@@ -19,6 +23,7 @@ const useFeaturesStepPresenter = (initialData?: IFeaturesStepFields) => {
watch,
formState: { errors },
getValues,
trigger,
} = useForm<IFeaturesStepFields>({
defaultValues: initialData || {
cards: [{ icon: "", title: "", description: "" }],
@@ -34,9 +39,10 @@ const useFeaturesStepPresenter = (initialData?: IFeaturesStepFields) => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
@@ -48,8 +54,6 @@ const useFeaturesStepPresenter = (initialData?: IFeaturesStepFields) => {
remove,
control,
watch,
getValues,
collectData,
};
};
@@ -1,3 +1,4 @@
import { ForwardedRef, useImperativeHandle } from "react";
import { useForm } from "react-hook-form";
interface FooterFormValues {
@@ -8,12 +9,16 @@ interface FooterFormValues {
copyright: string;
}
const useFooterStepPresenter = (initialData?: FooterFormValues) => {
const useFooterStepPresenter = (
initialData?: FooterFormValues,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
formState: { errors },
getValues,
trigger,
} = useForm<FooterFormValues>({
defaultValues: initialData,
});
@@ -22,17 +27,16 @@ const useFooterStepPresenter = (initialData?: FooterFormValues) => {
console.log(data);
};
const collectData = async () => {
return getValues();
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
handleSubmit,
onSubmit,
errors,
collectData,
getValues,
};
};
@@ -1,4 +1,5 @@
"use client";
import { ForwardedRef, useImperativeHandle } from "react";
import { useForm } from "react-hook-form";
export interface IHeroStepFields {
@@ -9,12 +10,16 @@ export interface IHeroStepFields {
gif_file: string;
}
const useHeroStepPresenter = (initialData?: IHeroStepFields) => {
const useHeroStepPresenter = (
initialData?: IHeroStepFields,
ref?: ForwardedRef<unknown>,
) => {
const {
register,
handleSubmit,
formState: { errors },
getValues,
trigger,
} = useForm<IHeroStepFields>({
defaultValues: initialData,
});
@@ -23,12 +28,16 @@ const useHeroStepPresenter = (initialData?: IHeroStepFields) => {
console.log(data);
};
useImperativeHandle(ref, () => ({
getData: () => getValues(),
validate: () => trigger(),
}));
return {
register,
handleSubmit,
errors,
onSubmit,
getValues,
};
};
@@ -0,0 +1,254 @@
"use client";
import { Step } from "@/components/ui/Stepper";
import { useEffect, useRef, useState } from "react";
import { useForm } from "react-hook-form";
interface IPageTypeFields {
type: "company" | "organization";
language?: string;
company_name?: string;
country?: string;
key: string;
}
const useMarketingForm = (
initialData: any,
onSubmit: (data: any) => void,
) => {
const [step, setStep] = useState(0);
const [formData, setFormData] = useState<any>(initialData || {});
const heroRef = useRef<any>(null);
const chartRef = useRef<any>(null);
const featuresRef = useRef<any>(null);
const challengesRef = useRef<any>(null);
const benefitsRef = useRef<any>(null);
const contactsRef = useRef<any>(null);
const footerRef = useRef<any>(null);
const {
register: registerPageType,
watch: watchPageType,
formState: { errors: pageTypeErrors },
getValues: getPageTypeValues,
setValue,
reset,
trigger: triggerPageType,
} = useForm<IPageTypeFields>({
defaultValues: {
type: "company",
},
});
useEffect(() => {
if (initialData) {
const formValues = {
type: initialData.type || "company",
language: initialData.language || "",
company_name: initialData.company_name || "",
country: initialData.country || "",
key: initialData.key || "",
};
reset(formValues);
setFormData(initialData);
}
}, [initialData, reset]);
const pageType = watchPageType("type");
const isCompany = pageType === "company";
const saveCurrentStepData = async () => {
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const keys = [
"hero",
"chart",
"features",
"challenges",
"advantages",
"contact",
"footer",
];
const currentRef = refs[step];
if (currentRef?.current?.getData) {
try {
const data = await currentRef.current.getData();
setFormData((prev: any) => ({
...prev,
[keys[step]]: data,
}));
} catch (error) {
console.error(`Error collecting data from step ${step}:`, error);
}
}
};
const collectAllStepsData = async () => {
await saveCurrentStepData();
const refs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const allData: any = { ...formData };
const keys = [
"hero",
"chart",
"features",
"challenges",
"advantages",
"contact",
"footer",
];
for (let i = 0; i < refs.length; i++) {
const ref = refs[i];
if (ref?.current?.getData) {
try {
const data = await ref.current.getData();
allData[keys[i]] = data;
} catch (error) {
console.error(`Error collecting data from step ${i}:`, error);
}
}
}
return allData;
};
const stepRefs = [
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
];
const validateCurrentStep = async () => {
const isPageTypeValid = await triggerPageType();
const currentRef = stepRefs[step];
const isStepValid = currentRef?.current?.validate
? await currentRef.current.validate()
: true;
return isPageTypeValid && isStepValid;
};
const validateAllSteps = async () => {
const isPageTypeValid = await triggerPageType();
const results = await Promise.all(
stepRefs.map((ref) =>
ref?.current?.validate ? ref.current.validate() : Promise.resolve(true),
),
);
return isPageTypeValid && results.every(Boolean);
};
const handleSubmitAll = async () => {
const isValid = await validateAllSteps();
if (!isValid) return;
const allData = await collectAllStepsData();
const pageTypeData = getPageTypeValues();
const finalData = {
...allData,
type: pageTypeData.type,
key: pageTypeData.key,
...(pageTypeData.type === "company" && {
language: pageTypeData.language,
company_name: pageTypeData.company_name,
country: pageTypeData.country,
}),
};
onSubmit(finalData);
};
const steps: Step[] = [
{
title: "Hero",
description: "Create the hero section",
content: null,
},
{
title: "Chart",
description: "Create the chart",
content: null,
},
{
title: "Features",
description: "Add features",
content: null,
},
{
title: "Challenges",
description: "Create challenges section",
content: null,
},
{
title: "Benefits",
description: "create benefits section",
content: null,
},
{
title: "Contacts",
description: "Create contacts section",
content: null,
},
{
title: "Footer",
description: "Add footer information",
content: null,
},
];
const handleStepChange = async (newStep: number) => {
if (newStep > step) {
const isValid = await validateCurrentStep();
if (!isValid) return;
}
await saveCurrentStepData();
setStep(newStep);
};
return {
step,
formData,
steps,
handleStepChange,
handleSubmitAll,
registerPageType,
pageTypeErrors,
isCompany,
setValue,
heroRef,
chartRef,
featuresRef,
challengesRef,
benefitsRef,
contactsRef,
footerRef,
};
};
export default useMarketingForm;