23.07.21
오늘 한 일
- Gloddy 개발
- GDG 회의
하루 요약
- 14:00 ~ 19:30 카공
- 20:00 ~ 01:00 집공
Gloddy 개발 - useFunnel
직접 구현
useFunnel
을 직접 구현해보았다. (시도해보았다)
왜? 그냥 토스꺼 쓰면 되는거 아냐?
토스에서 만든 useFunnel
은 내부적으로 next/router
를 사용하고 있다. 그래서 Next 13 버전에서는 사용할 수 없다.. 관련 이슈
그래서 직접 만들어보았다.
토스의 Slash
라이브러리의 useFunnel
훅을 참고하여 작성했다.
소스 코드
/* eslint-disable react/jsx-no-useless-fragment */
'use client';
import { useRouter, useSearchParams } from 'next/navigation';
import { Children, isValidElement, useEffect, useState } from 'react';
type NonEmptyArray<T> = [T, ...T[]];
interface FunnelProps {
children: React.ReactNode;
}
interface StepProps<Steps extends NonEmptyArray<string>> {
name: Steps[number];
children: React.ReactNode;
}
export function useFunnel<Steps extends NonEmptyArray<string>>(
steps: Steps,
options?: { initialStep?: Steps[number]; stepQueryKey?: string }
) {
const initialStep = options?.initialStep ?? steps[0];
const queryKey = options?.stepQueryKey ?? 'step';
const [step, setStep] = useState<Steps[number]>(initialStep);
const router = useRouter();
const searchParams = useSearchParams();
const nextStep = () => {
const currentIndex = steps.indexOf(step);
if (currentIndex < steps.length - 1) {
setStep(steps[currentIndex + 1]);
window.history.pushState(
null,
'',
`${window.location.pathname}?${queryKey}=${steps[currentIndex + 1]}`
);
}
};
const prevStep = () => {
const currentIndex = steps.indexOf(step);
if (currentIndex > 0) {
setStep(steps[currentIndex - 1]);
}
router.back();
};
const Funnel = ({ children }: FunnelProps) => {
const childrenArray = Children.toArray(children)
.filter(isValidElement)
.filter(child => {
return (child.props as StepProps<Steps>).name !== undefined;
});
childrenArray.forEach(child => {
if (!steps.includes((child.props as StepProps<Steps>).name)) {
throw new Error('스텝 이름이 잘못되었습니다.');
}
});
return <>{children}</>;
};
const Step = ({ name, children }: StepProps<Steps>) => {
return step === name ? <>{children}</> : null;
};
Funnel.Step = Step;
window.addEventListener('popstate', () => {
const currentStep = searchParams.get(queryKey) as Steps[number];
console.log('pop', currentStep);
if (currentStep) {
setStep(currentStep);
}
});
window.addEventListener('pushstate', () => {
const currentStep = searchParams.get(queryKey) as Steps[number];
console.log('push', currentStep);
});
useEffect(() => {
const currentStep = searchParams.get(queryKey) as Steps[number];
if (!currentStep) {
window.history.replaceState(
null,
'',
`${window.location.pathname}?${queryKey}=${initialStep}`
);
}
}, [initialStep, queryKey, searchParams]);
return { currentStep: step, Funnel, nextStep, prevStep } as const;
}
최대한 내 방식대로 해봤다. useFunnel
훅을 사용하면 다음과 같이 사용할 수 있다.
export default function FeedbackWrapper() {
const { Funnel, prevStep, nextStep } = useFunnel(['praise', 'mate']);
const { handleSubmit } = useFeedbackContext();
const onSubmit = (data: FeedbackRequestType) => {
console.log(data);
};
return (
<Funnel>
<Funnel.Step name="praise">
<PraiseComponent onPrevClick={prevStep} onNextClick={nextStep} />
</Funnel.Step>
<Funnel.Step name="mate">
<MateComponent onPrevClick={prevStep} onNextClick={handleSubmit(onSubmit)} />
</Funnel.Step>
</Funnel>
);
}
아직 구현되지 않은 부분
브라우저에서 뒤로가기를 눌렀을 때, step이 바뀌지 않는다.
popState
로 처리해서 뒤로 갈 때는 되는거같은데 앞으로 갈 때 또 안된다..쿼리스트링으로 현재 스텝을 표시해서, 새로고침을 했을 때도 현재 스텝을 유지할 수 있도록 해야한다.
그 외 최적화
Gloddy 개발 - react-hook-form 에러
react-hook-form
의 FormProvider
를 커스텀해서 사용하고 있었다.
근데 이런 에러가 떴다.
useFormContext
로 formState를 가져오니까 생긴 문제였다.
원인은 정확히 알 수 없어서 일단 임시방편으로 useFormState
로 control
을 받아와 해결했다.
오늘은 늦었으니 다음에 다시 정확한 원인을 찾아보자..!
내일 할 일
- Gloddy 개발