Oct 16, 2025
1 views
폼에서 가장 중요한 기능은 유저에게 데이터를 입력받는 것입니다. 그러면 멀티 스탭 폼은 일반 폼과 무엇이 다들지 궁금했습니다. 단어 그래도 스탭이 달랐는데, 스탭이 추가되면서 입력 데이터 관리 복잡도가 증가합니다. 특징은, 각 스텝마다 데이터는 군집을 이루고 군집에 순서가 정해집니다.
이 문제를 해결하는 가장 흔한 방법을 먼저 소개하겠습니다.
전역 상태를 활용하여 데이터를 저장하고 퍼널 순서를 자유롭게 결정한다.
const FirstStep = ({ isOpened, onNext }) => {
...
return (
...
<input name="id" onClick={(name)=> setTitle(name)}/>
<input name="password" onClick={(password)=> setTitle(password)}/>
<Button onClick={()=> {
...
onNext()
}}>
)
}
const FinalStep = ({ isOpened, onComplete }) => {
...
return (
...
<input name="phone" onClick={(phone)=> setPassword(phone)}/>
<Button onClick={()=> {
...
mutate({ id, password, phone }, {
onSuccess: {
onComplete() // 퍼널 닫기
}
}); // POST 전송
}}>
)
}
퍼널 상태 관리를 부모 컴포넌트에서 담당하고, 각 스텝은 다음 순서의 스텝을 열기 위한 콜백 함수를 주입 받습니다.
const [isFirstStepOpened, setIsFirstStepOpened] = useState(false);
const [isFinalStepOpened, setIsFinalStepOpened] = useState(false);
return <>
<FirstStep isOpened={isFirstStepOpened} onNext={()=> {
setIsFirstStepOpened(false);
setIsFinalStepOpened(true);
}}/>
<FinalStep isOpened={isFinalStepOpened} onComplete={()=> setIsFinalStepOpened(false)}/>
</>
코드 작성에 큰 어려움은 없습니다. 그리고 어떤 순서로 열고 닫히는지 눈에 잘 보이죠. 그런데 만약 스텝이 2개가 아닌 5개 혹은 그 이상으로 많아지면 어떨까요? 스텝을 열고 닫는 것도 관리 대상이 됩니다.
Stepper는 스텝 전환에 필요한 로직을 추상화하여 단순화합니다. Stepper 를 구현하는 다양한 방식이 있지만 핵심은 스텝을 순서대로 명시할 수 있어야하고 Stepper 는 입력받은 순서를 그대로 호출하는 원리이죠. 예시처럼 Step 컴포넌트를 순서대로 호출할 수도 있고, 배열을 통해 인터페이스로 주입하기도 합니다.
const [active, setActive] = useState(1);
const nextStep = () => setActive((current) => (current < 3 ? current + 1 : current));
const prevStep = () => setActive((current) => (current > 0 ? current - 1 : current));
return (
<>
<Stepper active={active} onStepClick={setActive} breakpoint="sm">
<Stepper.Step label="First step" description="Create an account">
<FirstStep onNext={nextStep} onPrev={prevStep}/>
</Stepper.Step>
<Stepper.Step label="Second step" description="Verify email">
<SecondStep onNext={nextStep} onPrev={prevStep}/>
</Stepper.Step>
<Stepper.Step label="Final step" description="Get full access">
<ThirdStep onNext={nextStep}/>
</Stepper.Step>
<Stepper.Completed>
<FinialStep />
</Stepper.Completed>
</Stepper>
</>
);
스텝 순서를 결정하는 책임이 Stepper 로 이동되었기 때문에, 이제 각 스텝이 다음 순서에서 어떤 스텝을 호출해야할지 신경쓰지 않고 nextStep 혹은 prevStep 만 호출하면 됩니다. 그런데, 만약 스텝 순서가 동적으로 변경된다면 어떻게 해야할까요? Stepper 로 이 문제를 풀 수 있을까요? 할 수 있겠지만 더 많은 고민을 필요로 합니다. 만약 SecondStep 다음에 ThirdStep1 혹은 ThirdStep2 가 호출되어야 한다면, 결국 SecondStep 이 다음 스텝의 순서를 알고있어야 합니다. ThirdStep 내부적으로 두 가지중 하나를 실행하던가, Stepper 에 입력되는 순서를 동적으로 변경해야 합니다. Stepper 는 순서를 신경쓰지 않도록 추상화한 것이 장점인데, 이 장점을 활용하지 못하고 결국 순서를 신경쓰게 만들죠.
// [FirstStep, SecondStep, ThirdStep1 or ThirdStep2, FinalStep] <- 동적 결정 로직이 추가되어야 한다.
한 가지 조건을 더 추가해보겠습니다. 만약 SecondStep 이후에 ThirdStep 이 오고 그 다음 ForthStep 이 온다면, 즉 4단계 스텝에서 5단계 스텝으로 변경되면 어떻게 처리해야할까요? 스텝을 동적으로 변경해주는 로직이 좀 더 복잡해 질 것으로 예상됩니다. SecondStep 에 위치한 상황에서 FirstStep 을 실수로 제거해버리면 강제로 다음 스텝으로 넘어갈 수 있기 때문에 의도치 않게 Step 을 점프할 수 도 있죠.
Stepper 는 '입력받은 스텝을 순서대로 호출한다' 이 개념을 추상화했기 때문에 순서대로 호출되지 않는 동적 멀티 스텝에는 어울리지 않는 것 같습니다.
// [FirstStep, SecondStep, FinalStep] or
// [FirstStep, SecondStep, ThirdStep, ForthStep, FinalStep]
순서와 상태 변경을 응집한다.