🔒
1 秒後に他画面へ移動してない場合のみリダイレクトしたい
ふとしです。
Next.js 入門中です。
たとえばセッションが切れているせいでアクセスできないページにアクセスした場合、無言でリダイレクトするよりはメッセージを出したりしてからリダイレクトしたい場合があります。
export const Middleware: FunctionComponent<Children> = ({ children }) => {
const router = useRouter();
const { getSession } = useAuth();
const redirect = useRedirect();
if (isAuthRequiredRoute(router) && !getSession()) {
redirect(1000, "/foo");
return (
<>
<h1>セッションが失効しているのでリダイレクトします。</h1>
</>
);
}
return (
<>
{children}
</>
);
};
単純には
export function useRedirect() {
const router = useRouter();
return (delay: number, ...args: Parameters<Router["replace"]>) => {
setTimeout(() => router.replace(...args), delay);
};
}
で実現できますが、そのページに他ページへのリンクが含まれる場合、リダイレクト先以外の画面に移動済みだったのにリダイレクトが発動してしまって、ユーザーから見れば嬉しくない遷移が発生し得ます。
現在のパスと一致する場合のみリダイレクトする
というわけで setTimeout
の発動時に、リダイレクトの原因となったパスと現在のパスを比較してからリダイレクトするかどうか決めるフックを書きます。
だめだった
ところで、以下は望む結果になりません。
export function useRedirect() {
const router = useRouter();
const from = router.asPath;
return (delay: number, ...args: Parameters<Router["replace"]>) => {
setTimeout(() => from === router.asPath && router.replace(...args), delay);
};
}
setTimeout
開始時の render で得られた router
と from
を参照しているので、常に比較が成功してリダイレクトします。
いけた
そこで useRef
を使います。
useRef
は
返されるオブジェクトはコンポーネントの存在期間全体にわたって存在し続けます。
なので、いつの段階の render でも同じものを参照できます。
遷移後の render で得られた router.asPath
を useRef
オブジェクトに挿入すると、すでに発行済の setTimeout
用の関数からでも、その未来の値が参照できるということになります。
export function useRedirect() {
const router = useRouter();
// setTimeout が呼び出された時点の値を参照できるように ref を使う。
const from = useRef("");
from.current = router.asPath;
return (delay: number, ...args: Parameters<Router["replace"]>) => {
setTimeout(
() => from.current === router.asPath && router.replace(...args),
delay
);
};
}
Discussion