브라우저 뒤로가기에 안전한 퍼널 만들기
퍼널 뜻 자체는 깔때기라는 뜻이지만, 웹 서비스에서 단계적으로 사용자의 반응을 끌어내는 형태도 퍼널이라고 해요.
주로 회원가입, 온보딩 같은 곳에서 연속적으로 작은 입력을 받는 형태로 많이 보셨을 거에요.
퍼널을 만들었는데 뒤로 가기를 하는 경우에 사용자가 이전에 입력한 값들이 유지가 되지 않아서 해결해보았어요.
간단한 퍼널을 뒤로 가기를 하더라도 기존의 데이터가 유지되는 형태로 만들어 볼게요.
Next.js를 이용하였고 모든 코드는 아래의 링크에서 확인하실 수 있어요.
구조
퍼널 구조는 아래처럼 구성했어요.
닉네임 입력 -> 팀 선택 -> 알림 설정 -> 완료
형태의 간단한 퍼널이에요.
퍼널들에서 얻은 인풋들을 모두 모아서 퍼널의 마지막에 요청하는 형태에요.
코드
export default function Home() { const [setup, setSetup] = useState<Setup>({ nickname: "", team: { a: false, b: false }, alarm: false, }); const router = useRouter(); const searchParams = useSearchParams(); const steps = searchParams.get(STEPS_PARAM_PREFIX) as Steps; const initRoute = useCallback(() => { router.replace(`?${STEPS_PARAM_PREFIX}=${FIRST_STEP}`); }, [router]); const nextStep = (steps: Steps) => { router.push(`?${STEPS_PARAM_PREFIX}=${steps}`); }; const completeStep = () => { router.push(COMPLET_ROUTE); }; const registSetup = ( regist: Partial<Setup> | ((setup: Setup) => Partial<Setup>), ) => { const next = typeof regist === "function" ? regist(setup) : regist; setSetup((prev) => ({ ...prev, ...next, })); }; useEffect(() => { initRoute(); }, [initRoute]); ... }
퍼널에서 사용하는 라우트로 진입 때 첫 퍼널로 쿼리 파라미터를 변경해요.
이후는 퍼널의 스텝에 따라서 컴포넌트를 그려주고 있어서 사용자가 버튼을 누르면 다음 스텝으로 진행돼요.
여러 퍼널들에서 사용하는 상태를 한곳에서 관리해 퍼널의 마지막 스탭에서 모아둔 상태를 사용해 요청하는 형태에요.
... return ( <main> <div className="flex flex-col items-center justify-between p-24"> {steps === "nickname" && ( <NicknameStep setup={setup} registSetup={registSetup} nextStep={() => nextStep("team")} /> )} {steps === "team" && ( <TeamStep setup={setup} registSetup={registSetup} nextStep={() => nextStep("alarm")} /> )} {steps === "alarm" && ( <AlarmStep setup={setup} registSetup={registSetup} completeStep={completeStep} /> )} </div> </main> );
퍼널들에서는 부모 컴포넌트에서 받은 상태를 사용하고 변경하기도 하는 형태로 이루어져 있어요.
뒤로 가기 동작 확인
function NicknameStep({ setup, registSetup, nextStep, }: { setup: Setup; registSetup: (regist: Partial<Setup>) => void; nextStep: () => void; }) { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const { value } = e.target; registSetup({ nickname: value }); }; return ( <div className="flex flex-col gap-y-8"> <label> <input type="text" value={setup.nickname} placeholder="닉네임을 입력해주세요." onChange={handleInputChange} className="border" /> </label> <NextStepButton onClick={nextStep} /> </div> ); }
뒤로 가기를 하게 되면 부모 컴포넌트에서 받은 상태를 그대로 사용하기 때문에 기존에 사용자가 입력한 값들이 유지가 돼요.
만약, 완료 후에 뒤로 가기를 하면 어떻게 될까요?
const FIRST_STEP: Steps = "nickname"; const initRoute = useCallback(() => { router.replace(`?${STEPS_PARAM_PREFIX}=${FIRST_STEP}`); }, [router]); ... useEffect(() => { initRoute(); }, [initRoute]);
첫 진입 때 첫 스텝으로 쿼리 파리미터를 바꿔주고, 바꿔준 스텝이 보이게 되요.
완료 페이지는 다른 라우트로 관리해서 뒤로 오더라도 저장해두었던 상태가 다시 보이지 않게 하고 있어요.
마무리
퍼널이 길어질수록 뒤로 가기에 대해서 주의해야해요.
사용자가 퍼널을 진행 중에 새로고침이나 뒤로 가기를 실수로 일으켜 입력한 값들이 사라지면 경험상 매우 좋지 않아요.
본 블로그의 과정은 구현을 위한 방법이고, 자주 사용되는 부분을 추상화시켜서 사용할 수도 있어 보여요.
또, 여러 사양이 들어가면 이것보다 더 복잡하게 구현해야 할수도 있어요.
실제로 완료 후에 뒤로 가면 다른 스텝이 잠깐 보이는 문제가 있어요.
그래서, 간단한 퍼널이라면 위와 같은 방법으로 접근해 보아도 좋을 것 같네요.