1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

`<a href>` と React Router `<Link>` — ページ遷移でstateが消える問題

1
Posted at

背景

この教材をやっていた時に、ログインしたユーザーの情報をuseContextで保持して各画面で表示する、という機能で詰まったのでアウトプットしておく

発生した問題

ヘッダーメニューのリンクをクリックすると、stateで保存していたはずのloginUsernullになってしまっていた

ログイン後、HomeコンポーネントではloginUserにユーザーデータが入っていた

前提:stateの管理構造

まず「何が消えるのか」を理解するために、ログインユーザーの状態管理の仕組みを見る。

LoginUserProvider.tsx — stateの定義

useStateContextを組み合わせて、ログインユーザーの情報をアプリ全体で管理している

// LoginUserProvider.tsx
import { createContext, useState } from 'react';

import type { User } from '../types/api/user';

export type LoginUserContextType = {
  LoginUser: User | null
  setLoginUser: (user: User) => void
}

export const LoginUserContext = createContext<LoginUserContextType | undefined>(undefined);

export const LoginUserProvider = (props: { children: React.ReactNode }) => {
  const { children } = props
  const [LoginUser, setLoginUser] = useState<User | null>(null);

  return (
    <LoginUserContext.Provider value={{ LoginUser, setLoginUser }}>
      {children}
    </LoginUserContext.Provider>
  );
};

ログイン処理の中でsetLoginUser({ id: 1, name: "太郎", email: "taro@example.com" })のようにデータを格納している

Router.tsx — Providerがルートを囲む構造

// Router.tsx
import { BrowserRouter, Route, Routes } from "react-router-dom";
import { LoginUserProvider } from "./providers/LoginUserProvider";
import { Home } from "./components/pages/Home";
import { UserManagement } from "./components/pages/UserManagement";

export const Router = () => {
  return (
    <BrowserRouter>
      <LoginUserProvider>
        <Routes>
          <Route path="/home" element={<Home />} />
          <Route path="/home/user_management" element={<UserManagement />} />
        </Routes>
      </LoginUserProvider>
    </BrowserRouter>
  );
};

LoginUserProviderRoutes全体を囲んでいるので、/homeから/home/user_managementに遷移してもProviderがアンマウントされなければstateは保持される(今回詰まったところ)

原因:Chakra UIの<Link><a href>になっていた

Header.tsxで画面遷移のリンクを書く際、Chakra UIのLinkコンポーネントを使っていた

// ❌ 修正前:Header.tsx
import { Link } from "@chakra-ui/react";

export const Header = () => {
  return (
    <nav>
      <Link href="/home">ホーム</Link>
      <Link href="/home/user_management">ユーザー一覧</Link>
    </nav>
  );
};

Chakra UIの<Link href="...">は、内部的にHTMLの<a href="...">タグをレンダリングする。これがstateが消える原因だった

なぜ<a href>だとstateが消えるのか

ブラウザの通常の画面遷移(<a href>

ユーザーがリンクをクリック
  ↓
ブラウザが新しいURLにHTTPリクエストを送る
  ↓
サーバーからHTMLを取得する
  ↓
ページ全体を最初から読み込み直す(フルリロード)
  ↓
JavaScriptも最初から実行し直す
  ↓
Reactアプリが最初から起動する
  ↓
useState の初期値(null)がセットされる
  ↓
ログイン時にsetLoginUserで入れたデータは消えている

これは、ブラウザのアドレスバーにURLを打ち込んでEnterを押すのと同じ動作。ページ全体が破棄されて、一から作り直される

SPAの画面遷移(React Routerの<Link>

ユーザーがリンクをクリック
  ↓
React Router が <a> タグのデフォルト動作を preventDefault() で止める
  ↓
ブラウザにHTTPリクエストを送らせない(サーバーにアクセスしない)
  ↓
JavaScriptでURLだけを書き換える(History API を使用)
  ↓
React Router が新しいURLに対応するコンポーネントだけを差し替える
  ↓
ページ全体はリロードされない
  ↓
LoginUserProvider は生きたまま(アンマウントされない)
  ↓
useState のデータはそのまま保持される

解決:React Routerの<Link>に変更

// ✅ 修正後:Header.tsx
import { Link } from "react-router-dom";

export const Header = () => {
  return (
    <nav>
      <Link to="/home">ホーム</Link>
      <Link to="/home/user_management">ユーザー一覧</Link>
    </nav>
  );
};

変更点は2つ

  1. import { Link } from "@chakra-ui/react"import { Link } from "react-router-dom"
  2. href prop → to prop

これで画面遷移してもstateが保持されるようになった。

デバッグ方法について

問題の切り分けで役立ったポイントも残しておく

フルリロードが起きている場合

遷移するたびにこのログが出る

[vite] connecting...
[vite] connected.

これはViteの開発サーバーとのWebSocket接続が新しく確立されたことを意味する。
つまり、ページが最初から読み込み直されている

SPA遷移の場合

本来、Reactの場合は上記のログは表示されない。コンポーネントの差し替えだけが行われるので、Reactのログだけが出る

まとめ

<a href> React Router <Link>
ページリロード する しない
useState のデータ 消える 残る
サーバーへのリクエスト 発生する 発生しない
内部の仕組み ブラウザのデフォルト動作 History API + preventDefault
使うべき場面 外部サイトへのリンク アプリ内の画面遷移

感想

  • そもそもReactを理解できていないから詰まった、理解不足だった
  • Viteのコンソールログ([vite] connecting...)でフルリロードが起きているかどうかを判断できるのは、今後のデバッグでも使えそう

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?