안녕하세요~ 👋 예전에 작성해두었던 리액트 뒤로가기 제어 문서인데 이제야 올리네요. 참고하셔서 좋은 리액트 앱을 만드시길 바래요~
// Test2.tsx
const Test2 = () => {
const history = useHistory()
useBlock(history.location) // 뒤로가기 막기
return (
<>
<div>Test2</div>
<div>
<button onClick={history.goBack}>Back</button>
</div>
</>
)
}
export default Test2
// useBlock.ts
// location: 뒤로가기 막을 페이지 location
const useBlock = (location: Location | Pathname) => {
const history = useHistory()
useEffect(() => {
const unlisten = history.listen((_, action) => {
// 리스너 해제
unlisten()
// 뒤로가기 시 동작
if (action === 'POP') {
history.push(location)
}
})
}, [history, location])
}
export default useBlock
// Test3.tsx
const Test3 = () => {
const { setHash, removeHash, HashElement } = useHash()
return (
<Test3Wrapper>
<Button onClick={() => setHash('modal')}>모달 열기</Button>
<HashElement>
<Modal>
<div>모달입니다.</div>
<Button onClick={() => setHash('other')}>해쉬추가</Button>
<Button onClick={removeHash}>닫기</Button>
</Modal>
</HashElement>
</Test3Wrapper>
)
}
/**
* hash를 추가하여 뒤로가기 UX 제어
*/
const useHash = () => {
const history = useHistory()
const hashString = history.location.hash.replace('#', '')
const hashArray = hashString.split('#')
// 해쉬추가
const setHash = (hash: string) => {
history.push({ hash: hashString ? `${hashString}#${hash}` : hash })
}
// 해쉬제거
const removeHash = () => history.goBack()
// 해쉬엘리먼트
const HashElement = ({ children }: { children: ReactNode }) => {
return <>{hashString && hashArray.map(() => children)} </>
}
return { setHash, removeHash, HashElement, hash: hashString }
}
export default useHash
"use client";
import { Route } from "next";
import {
ReadonlyURLSearchParams,
usePathname,
useRouter,
useSearchParams,
} from "next/navigation";
import { useCallback, useLayoutEffect, useState } from "react";
/**
* 브라우저 뒤로가기를 막아주는 훅.
* removeBlocking: 블로킹 해제.
* resumeBlocking: 블로킹 재개.
*/
export function useBlockToBack() {
// blocking 상태
const [activate, setActivate] = useState(true);
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
// search에 block-to-back 추가하는 함수
const addBlocking = useCallback(() => {
if (!activate) {
return;
}
const queryString = createQueryString(
searchParams,
"block-to-back",
"true"
);
const pathnameWithSearch = (pathname + "?" + queryString) as Route;
if (searchParams.get("block-to-back") == null) {
router.push(pathnameWithSearch);
}
}, [activate, pathname, router, searchParams]);
// block-to-back 제거
function removeBlocking() {
if (!activate) {
return;
}
setActivate(false);
router.back();
}
// block-to-back 다시 추가
const resumeBlocking = () => {
if (activate) {
return;
}
setActivate(true);
const queryString = createQueryString(
searchParams,
"block-to-back",
"true"
);
const pathnameWithSearch = (pathname + "?" + queryString) as Route;
if (searchParams.get("block-to-back") == null) {
router.push(pathnameWithSearch);
}
};
useLayoutEffect(() => {
addBlocking();
}, [addBlocking]);
return { removeBlocking, resumeBlocking };
}
// 쿼리스트링의 search 변경
export function createQueryString(
searchParams: ReadonlyURLSearchParams,
name: string,
value: string | null
) {
const params = new URLSearchParams(searchParams);
if (value === null) {
params.delete(name);
} else {
params.set(name, value);
}
return params.toString();
}
"use client";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Button } from "~/components/Button";
import { useBlockToBack } from "../useBlockToBack";
export default function BlockingPage() {
const router = useRouter();
const { removeBlocking, resumeBlocking } = useBlockToBack();
return (
<div>
<h1 className="headline1">Blocking page</h1>
<br />
<Button color="primary" onClick={() => router.back()}>
뒤로
</Button>
<br />
<br />
<Button color="primary" onClick={() => removeBlocking()}>
블로킹제거
</Button>
<br />
<br />
<Button color="primary" onClick={() => resumeBlocking()}>
블로킹재개
</Button>
<br />
<br />
<Button asChild color="primary">
<Link href={"/other"}>/other</Link>
</Button>
</div>
);
}
// SomePage.tsx
const preventClose = (e: BeforeUnloadEvent) => {
e.preventDefault()
e.returnValue = '' // for chrome. deprectaed.
}
useEffect(() => {
window.addEventListener('beforeunload', preventClose)
return () => {
window.removeEventListener('beforeunload', preventClose)
}
}, [])
읽어주셔서 감사해요~ 뒤로가기의 원리를 이해하는게 중요한 것 같아요. 다들 좋은 하루 되세요~ 👋😄