🔒

1 秒後に他画面へ移動してない場合のみリダイレクトしたい

2022/05/28に公開

ふとしです。

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 で得られた routerfrom を参照しているので、常に比較が成功してリダイレクトします。

いけた

そこで useRef を使います。

useRef

返されるオブジェクトはコンポーネントの存在期間全体にわたって存在し続けます。

なので、いつの段階の render でも同じものを参照できます。

遷移後の render で得られた router.asPathuseRef オブジェクトに挿入すると、すでに発行済の 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