이번 앱잼을 진행하며 저는 두개의 페이지를 맡았습니다. 하나는 게스트인 사람이 호스트 신청을 위해 작성해야 하는 다양한 질문에 대한 입력폼을 다루는 페이지이고, 다른 하나는 호스트가 자신만의 모임을 개설하기 위해 작성해야 하는 다양한 질문에 대한 입력폼을 다루는 페이지입니다.
하나는 총 3단계, 다른 하나는 총 4단계의 Step을 가지고 있고, 한 페이지에 꽤 많은 데이터를 다루고 있어 꽤나 복잡해보입니다.
이번 앱잼을 진행하기 전 사전 과제로 funnel구조를 사용하여 회원가입 로직 구현해보기 과제를 진행했는데 이때 퍼널구조 자료들을 찾아보다가 TOSS | SLASH 23 - 퍼널 : 쏟아지는 페이지 한 방에 관리하기 영상을 보게 되었습니다. 이미 토스에서 이러한 사용자 입력폼을 위한 퍼널구조의 대부분을 구현해놨고, 라이브러리화 시켜놓은것을 보고 많은 도움을 받았습니다.
퍼널구조에도 다양한 방식이 있습니다. 하나의 공통된 URL로 모든 퍼널구조의 페이지들을 다루는 방법이 있고, path parameter를 추가해서 각각 다른 URL을 가진 페이지로 만드는 방식이 있는데 이번 프로젝트에서는 다음 페이지로 넘어갈때마다 path parameter를 갈아끼워 다른 URL이 되도록 하는 방식을 채택했습니다. 이와 같이 퍼널구조를 구현하며 고려했던 부분들은 아래와 같습니다.
import { ReactElement, ReactNode, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
export interface StepProps {
name: string;
children: ReactNode;
}
export interface FunnelProps {
children: Array<ReactElement<StepProps>>;
}
export const useFunnel = (defaultStep: string, basePath: string) => {
const [step, setStep] = useState(defaultStep);
const navigate = useNavigate();
const { step: urlStep } = useParams<{ step: string }>();
useEffect(() => {
if (urlStep) {
setStep(urlStep);
}
}, [urlStep]);
const Step = (props: StepProps): ReactElement => {
return <>{props.children}</>;
};
const Funnel = ({ children }: FunnelProps) => {
const targetStep = children.find((childStep) => childStep.props.name === step);
return <>{targetStep}</>;
};
const nextStep = (next: string) => {
setStep(next);
navigate(`/${basePath}/${next}`);
};
return { Funnel, Step, setStep, nextStep, currrendStep: step } as const;
};
위 코드는 funnel구조를 다루기 위한 커스텀 훅 코드입니다. Step : 각 step에 대한 페이지 컴포넌트를 담을 컴포넌트
Funnel : 여러 Step들을 담고, step의 name값을 받아 어떤 Step을 보여줄지 확인
nextStep : step값을 다음 step값으로 변경해주고, 다음 페이지로 라우팅 해줄 함수
import { LogoHeader } from '@components';
import ClassPost from '@pages/class/components/ClassPost/ClassPost';
import { useFunnel } from 'src/hooks/useFunnel';
import { classPostPageLayout } from './ClassPostPage.style';
import { Provider } from 'jotai';
import { useParams } from 'react-router-dom';
const steps = ['step1', 'step2', 'step3', 'finish'];
const ClassPostPage = () => {
const { step } = useParams<{ step: string }>();
const { Funnel, Step, nextStep } = useFunnel(step || steps[0], 'class/post');
return (
<Provider>
<LogoHeader />
<div css={classPostPageLayout}>
<ClassPost steps={steps} nextClickHandler={nextStep} Funnel={Funnel} Step={Step} />
</div>
</Provider>
);
};
export default ClassPostPage;