SPA 방식에서 페이지 이탈 감지하기
React를 사용하여 SPA의 router를 관리하기 위한 라이브러리로 React-router-dom을 많이 사용한다.
이번에는 SPA 에서 React-router-dom에서 제공하는 useBlocker를 활용하여 페이지 이탈을 감지하고 이를 유저에게 다이얼로그로 알려주는 코드를 작성해보려고 한다.
준비물
- React-router-dom V6 설치
npm install react-router-dom@6
- v6 부터 useBlocker를 사용할 수 있다.
상황
- 유저가 입력값을 수정할 수 있는 페이지가 존재한다.
- 유저가 입력값을 수정한 이후 "임시저장"을 하지 않은 상태로 현재 페이지를 이탈할 경우, 다이얼로그를 띄워 알림을 준다.
접근 방법
- 유저가 입력값을 수정했다는 상태값 선언
function EditPage() {
const [isFormDirty, setIsFormDirty] = useState(false);
const onFormDirtyChange = useCallback((isDirty: boolean) => {
setIsFormDirty(isDirty);
}, []);
return (
<div>
<EditTableComponent onFormDirtyChange={onFormDirtyChange} />
</div>
)
}
function EditTableComponent({onFormDirtyChange}) {
const methods = useForm();
// Input 값 초기화되면 isFormDirty값 false로 초기화
useEffect(() => {
methods.reset();
onFormDirtyChange(false);
}, [methods, onFormDirtyChange]);
// form 값 변경 감지
useEffect(() => {
const subscription = methods.watch(() => {
onFormDirtyChange(true);
});
return () => {
subscription.unsubscribe();
};
}, [methods, onFormDirtyChange]);
return (
<FormProvider {...methods}>
...
</FormProvider>
)
}
- useBlocker 훅 생성
import {useNavigate,unstable_useBlocker as useRouterBlocker} from 'react-router-dom';
export function useBlocker(
shouldBlock: (args: {
currentLocation: Location;
nextLocation: Location;
}) => boolean,
) {
const blocker = useRouterBlocker(shouldBlock);
const navigate = useNavigate();
return {
...blocker,
reset: () => {
blocker.reset?.();
},
proceed: () => {
blocker.reset?.();
if (blocker.location) {
navigate(blocker.location.pathname);
}
},
};
}
useBlocker 훅은 unstable_useBlocker
함수를 사용하고 boolean 값을 반환하는 shouldBlock 함수를 인자로 받아 blocker 객체를 반환한다.
reset 메서드는 blocker 객체를 초기화하고, proceed 객체는 사용자의 행동(페이지 이동)을 처리한다.
- useBlocker 훅을 통해 다이얼로그 띄우기
function EditPage() {
const [isFormDirty, setIsFormDirty] = useState(false);
const [showLeaveDialog, setShowLeaveDialog] = useState(false);
const onFormDirtyChange = useCallback((isDirty: boolean) => {
setIsFormDirty(isDirty);
}, []);
const blocker = useBlocker(({ currentLocation, nextLocation }) => {
if (isFormDirty && currentLocation.pathname !== nextLocation.pathname) {
setShowLeaveDialog(true);
return true;
}
return false;
});
return (
<div>
<EditTableComponent onFormDirtyChange={onFormDirtyChange} />
<Dialog
isShow={showLeaveDialog}
onClose={() => {
setShowLeaveDialog(false);
blocker.reset();
}}
className={styles.dialogContent}
icon={<Icons_warning1 className={styles.warningIcon} />}
header={t('알림')}
footer={
<>
<Button
type="button"
color="primary"
size="md"
onClick={() => {
setShowLeaveDialog(false);
blocker.reset();
}}
>
{t('머무르기')}
</Button>
<Button
type="button"
color="secondary"
size="md"
onClick={() => {
setShowLeaveDialog(false);
setIsFormDirty(false);
blocker.proceed();
}}
>
{t('나가기')}
</Button>
</>
}
>
<p>{t('페이지를 벗어나시겠습니까?')}</p>
<p>{t('저장되지 않은 변경사항은 사라집니다.')}</p>
</Dialog>
</div>
)
}
회고
이번 기회에 SPA 방식에서 페이지 이탈을 감지하고 다이얼로그로 사용자에게 알림을 주는 기능을 구현했다.
처음 해보는 거라 생각을 많이 하게 되었고 앞으로 자주 사용하게 될 것 같아 hook으로 만들었다.
내가 만든 코드가 누군가에 큰 도움이 되었으면 좋겠다.
'Frontend' 카테고리의 다른 글
FormData 형식으로 multipart 데이터 PUT 요청하기 (0) | 2025.04.30 |
---|---|
[R3F] 3D 객체 겹쳐보이는 이슈 z-fighting 해결하기 (0) | 2025.03.04 |
[Sass] 다크 모드, 라이트 모드 구현하기 (1) | 2024.06.10 |
[Storybook] 라이트모드, 다크모드 토글 기능 추가하기 (0) | 2024.06.10 |
"jsx-a11y/no-noninteractive-element-interactions" 에러 해결 (0) | 2024.05.11 |