I'm trying to create a multi-step form and I'm putting the data in a separate file for constants like this
import {lazy} from "react";
export const steps = [ { id: 0, name: 'Personal Info', component: lazy(() => import('../components/PersonalInfo')), }, ];
I pass it to the custom hook in the context
const dataSteps = useMultiStep(steps); const { next, back, currentStep, isFirst } = dataSteps;
This is a custom hook
import { useState } from 'react'; import { MultistepProps } from '../@types/Multiform'; const useMultiStep = (steps: MultistepProps[]) => { const [step, setStep] = useState(0); const isLast = step === steps?.length - 1; const isFirst = step === 0; const next = (): void => { if (isLast) return; setStep((current) => current + 1); }; const back = (): void => { if (isFirst) return; setStep((current) => current - 1); }; return { step, next, back, currentStep: steps[step], isFirst, }; }; export default useMultiStep;
I am using dynamic components here
import FormInterface from './interface/FormInterface'; import useApp from './hooks/useApp'; import { Suspense } from 'react'; function App() { const data = useApp(); const { currentStep, next, back, isFirst } = data; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); next(); }; return ( <FormInterface> <form onSubmit={handleSubmit} className="flex flex-col h-full py-5 group" noValidate={true} > {currentStep.component && ( <> <h1 className="text-3xl font-bold text-marineBlue"> {currentStep?.name} </h1> <Suspense fallback={<div>Loading...</div>}> <currentStep.component /> //here </Suspense> <div className={`mt-4 sm:mt-auto flex ${ isFirst ? 'justify-end' : 'justify-between' }`} > <button type="button" className={`hover:text-marineBlue font-bold text-coolGray py-2 px-5 rounded-md text-[13px] ${ isFirst ? 'hidden' : 'block' }`} onClick={back} > Go Back </button> <button type="submit" className="hover:bg-purplishBlue bg-marineBlue text-white py-2 px-5 rounded-md text-[12px] group-invalid:pointer-events-none group-invalid:opacity-30 self-end" > Next Step </button> </div> </> )} </form> </FormInterface> ); } export default App;
My vite configuration is like this
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; // https://vitejs.dev/config/ export default defineConfig({ plugins: [react()], });
After trying everything, I still get this error, not on the first render, but when the component reloads
App.tsx:7 Uncaught TypeError: Property 'currentStep' of 'data' cannot be destructured because it is null. In the application (App.tsx:7:11) in renderWithHooks (react-dom.development.js:16305:18) in mountInminatedComponent (react-dom.development.js:20074:13) At start working (react-dom.development.js:21587:16) at HTMLUnknownElement.callCallback2 (react-dom.development.js:4164:14) at Object.invokeGuardedCallbackDev (react-dom.development.js:4213:16) in invokeGuardedCallback (react-dom.development.js:4277:31) at beginWork$1 (react-dom.development.js:27451:7) in PerformUnitOfWork (react-dom.development.js:26557:12) Working in LoopSync (react-dom.development.js:26466:5)
I believe this is an HRM issue because it's like loading the entire page with just the components because the state is lost and the information on useMultisteps
is lost but I just can't find a way Ways to make it work, please help me and teach me a better way to accomplish what I want to do
P粉5485126372023-11-11 11:35:58
Your state seems to be lost when you update the component (probably because the useApp() hook returns null
before the data is ready).
For example wrap the useApp hook in a useMemo hook to ensure it is only called once:
const data = useMemo(() => useApp(), []);
In summary, this will ensure that the useApp hook is only called once and its return value is remembered, even if the component is re-rendered due to HMR.
Second suggestion: Try modifying your code like this:
const { currentStep, next, back, isFirst } = data ?? {};
This will ensure that the destructuring operation only occurs when the data object is not empty.