当該記事のサンプルコードはこちら(Github)。
アプリはこちら(Outlet-Test)
- Reactのページ遷移はreact-router-domを使ってやる
- 指定したURLのパスのコンポーネントを表示する
- <Outlet />を利用して画面のセキュリティを保つ
- 前準備:サインイン状態の確保(contextの設定)
- const [signIn, setSignIn] = useState(false);
- window.localStorage.getItem(“outletAppSignIn”) ? setSignIn(true) : setSignIn(false)
- const [signIn, setSignIn] = useSignInContext();
- setSignIn(true);
- window.localStorage.setItem(“outletAppSignIn”,true);
- navigate(“/”);
- window.localStorage.removeItem(“outletAppSignIn”);
- setSignIn(false);
- navigate(‘/signin’);
- 画面遷移について
- <Outlet />を使って、SignIn状態の有無における画面管理をする
- 前準備:サインイン状態の確保(contextの設定)
- 最後に
Reactのページ遷移はreact-router-domを使ってやる
ユーザー登録をして、何かサービスを行うアプリには、SignIn/SignUp/Home画面が必須。
これを、Reactでは、react-router-domを使って、ブラウザでアクセスしたURLのパスを元にルーティングして、パスに紐づいているコンポーネントを表示します。
「~URL~/signin」にはSignInコンポーネントを作成して、「~URL~/signup」にはSignUpコンポーネントを作成して、「URL~/」にはHomeコンポーネントをするわけですね。
ですから画面遷移をしたい方は、Reactのプロジェクトを作成したのち、react-router-domをインストールしていない場合は、下記のコマンドでインストールしてください。
npm install react-router-dom
指定したURLのパスのコンポーネントを表示する
シンプルなルーティングのサンプル
まずは、Home,SignIn,SignInのコンポーネントを作りましょう。

// SignUp.js
import { useNavigate } from "react-router-dom";
const SignUp = () => {
const navigate = useNavigate();
return (
<div>
<h2>SignUp画面です</h2>
<button onClick={() => navigate('/signin')}>SignUp</button>
</div>
);
};
export default SignUp;

// SignIn.js
import { useNavigate } from "react-router-dom";
const SignIn = () => {
const navigate = useNavigate();
return (
<div>
<h2>SignIn画面です</h2>
<button onClick={() => {navigate("/")}}>SignIn</button>
<button onClick={() => {navigate("/signup")}}>SignUp</button>
</div>
);
};
export default SignIn;

// Home.js
import { useNavigate } from "react-router-dom";
const Home = () => {
const navigate = useNavigate();
return (
<div>
<h2>Home画面です</h2>
<button onClick={() => navigate('/signin')}>SignOut</button>
</div>
);
};
export default Home;

そして、App.jsは以下のようになります。
// App.js
import React from "react";
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./components/Home";
import SignIn from "./components/SignIn";
import SignUp from "./components/SignUp";
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home/>} />
<Route path="/signup" element={<SignUp />} />
<Route path="/signin" element={<SignIn />} />
</Routes>
</BrowserRouter>
);
}
export default App;
キモは、App.jsです。
「<BrowserRouter>」の配下でルーターは機能します。
<BrowserRouter>をreact-routerの公式ドキュメントで検索すると、
A”<BrowserRouter>” stores the current location in the browser’s address bar using clean URLs and navigates using the browser’s built-in history stack.
“<BrowserRouter>”は、クリーンなURLを使用してブラウザのアドレスバーに現在地を保存し、ブラウザの内蔵履歴スタックを使用してナビゲートします。
と、はっきり何言っているか(私には)よくわかりません。
とりあえず、
<BrowserRouter>
<Routes>
<Route path="/" element={<Xxxx/>} />
<Route path="/yyyy" element={<Yyyy />} />
<Route path="/zzzz" element={<Zzzz />} />
</Routes>
</BrowserRouter>
という風に<BrowserRouter>、<Routes>、<Route/>のセットで覚えておくのがいいんじゃないかと思います。
<BrowserRouter>は、Reactプロジェクトの中で一度しか使えません。
だから、App.jsで使うのが、使い易いでしょう。
そして、React Routerは<BrowserRouter>の中でしか使えません。
このサンプルの問題点
上記のサンプルには問題点があります。
既にサインイン状態にもかかわらず、ブラウザに”/signin”などのパスを入力すると、当該画面に遷移してしまう。
ということですね。
などのセキュリティ上の問題になります。
そこで、サインイン/アウトの状態で画面のセキュリティを保てるようにルーティングを行います。
<Outlet />を利用して画面のセキュリティを保つ

前準備:サインイン状態の確保(contextの設定)
ここで、「サインインしているよ」という状態を保存しておくことと、各コンポーネントからサインインの状態を参照するために、contextを作成します。
// src/context/signinContext.js
import { createContext, useContext, useEffect, useState } from "react";
const signInContext = createContext();
export const useSignInContext = () => useContext(signInContext);
export const SignInProvider = ({ children }) => {
const [signIn, setSignIn] = useState(false);
useEffect(() => {
window.localStorage.getItem("outletAppSignIn") ? setSignIn(true) : setSignIn(false);
},[]);
return (
<signInContext.Provider value={ [signIn, setSignIn]}>
{children}
</signInContext.Provider>
);
};
const [signIn, setSignIn] = useState(false);
サインインの状態を管理するステート。
「signIn」がtrueのとき、サインインしている、ということになります。
「signIn」を更新する時、「setSiginIn」を使用します。
window.localStorage.getItem(“outletAppSignIn”) ? setSignIn(true) : setSignIn(false)
画面が再読み込みされたときのために、localStorageにサインインの状態を保存しています。
そこから、サインインの状態を確保して、「signIn」に反映しています。
サインイン処理の時にlocalStorageにtrueをセットして、サインアウトの時にlocalStorageからremoveItemしています。
// src/component/SignIn.js
import { Navigate, useNavigate } from "react-router-dom";
import { useSignInContext } from "../context/signinContext";
const SignIn = () => {
const [signIn, setSignIn] = useSignInContext();
const navigate = useNavigate();
const handleSignIn = () => {
setSignIn(true);
window.localStorage.setItem("outletAppSignIn",true);
navigate("/");
}
return (
<>
{signIn ? (<Navigate to="/" />) : (
<div>
<h2>SignIn画面です</h2>
<button onClick={handleSignIn}>SignIn</button>
<button onClick={() => {navigate("/signup")}}>SignUp</button>
</div>
)}
</>
);
};
export default SignIn;
「SignIn」ボタンをクリックすると、「setSignIn」を使って「signIn」をtrueして、Homeコンポーネントを表示します。
const [signIn, setSignIn] = useSignInContext();
contextから、サインインしているかどうかのフラグを更新する関数「setSignIn」をもとめています。
setSignIn(true);
signInボタンがクリックされたので、「signIn」フラグをtrueにして、サインイン状態をアプリに保管しています。
window.localStorage.setItem(“outletAppSignIn”,true);
ローカルストレージにもサインイン状態をセットします。
プログラムが再読み込みされても、ログイン状態を保持するためです。

ローカルストレージにサインアップ状態を保持できるんなら、useStateで保管しておかなくていいんじゃないか?
とも考えられますが、ローカルストレージにアクセスするよりも、メモリにアクセスするが速いのuseStateを使いました。
navigate(“/”);
サインインが終了したので(このプログラムでは「signIn」をtrueにしただけ。)、ホーム画面に遷移しています。
// src/component/Home.js
import { useNavigate } from "react-router-dom";
import { useSignInContext } from "../context/signinContext";
const Home = () => {
const [signIn, setSignIn] = useSignInContext();
const navigate = useNavigate();
const handleSignOut = () => {
window.localStorage.removeItem("outletAppSignIn");
setSignIn(false);
navigate('/signin');
}
return (
<div>
<h2>Home画面です。{signIn ? '(サインイン)' : '(不正サインイン)'}</h2>
<button onClick={handleSignOut}>SignOut</button>
</div>
);
};
export default Home;
「SignOut」ボタンをクリックすると、「setSignIn」を使って「signIn」をfalseして、SignInコンポーネントを表示します。
window.localStorage.removeItem(“outletAppSignIn”);
サインアウトしたので、ローカルストレージのサインインをしているフラグを削除しています。
setSignIn(false);
signOutボタンがクリックされたので、「signIn」フラグをfalseにして、サインアウト状態をアプリに保管しています。
navigate(‘/signin’);
サインアウトしたので、サインイン画面に遷移しています。
画面遷移について
では、どのように画面を表示すればよいのか。
以下にまとめました。
<Outlet />を使って、SignIn状態の有無における画面管理をする
App.js:画面遷移はこのモジュールから始まる
「signIn」フラグの状態を見て、画面の遷移の制御をします。
// App.js
import React from "react";
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./components/Home";
import SignIn from "./components/SignIn";
import SignUp from "./components/SignUp";
import { SignInProvider } from "./context/signinContext";
import PrivateRoute from "./components/PrivateRote";
import PublicRoute from "./components/PublicRoute";
function App() {
return (
<SignInProvider>
<BrowserRouter>
<Routes>
<Route path="/" element={
<PrivateRoute>
<Home />
</PrivateRoute>} />
<Route element={<PublicRoute />}>
<Route path="/signup" element={<SignUp />} />
<Route path="/signin" element={<SignIn />} />
<Route path="*" element={<div><h1>404 ページが見つかりません。</h1></div>} />
</Route>
</Routes>
</BrowserRouter>
</SignInProvider>
);
}
export default App;
「<SignInProvider></SignInProvider>」はContextを参照するためのコンポーネントです(こちらを参照)。
「<BrowserRouter></BrowserRouter>,<Routes></Routes>」については、こちらを参照してください。
<Route path=”/” element=<PrivateRoute><Home /></PrivateRoute>} />
“/”のパスが指定されると<Route path=”/” element=<PrivateRoute><Home /></PrivateRoute>} />コンポーネントから<PrivateRoute>コンポーネントに制御が渡されます。
<Route element={<PublicRoute />}>
“/”以外のパスが指定されると<Route element={<PublicRoute />}>コンポーネントから<PublicRoute >コンポーネントに制御が渡されます。
<PublicRoute >コンポーネントの中でパスの振り分けをするか、<Route element={<PublicRoute />}>配下のパスに振り分けるかの判断が行われます(PublicRoute.jsの解説を参考にしてください)。
PrivateRoute.js:”/”のパスが指定されたときに、パスの振り分けを行うモジュール
“/”のパスが指定されたとき、App.jsから展開される「PrivateRoute」コンポーネントのコードです。
// src/components/PrivateRoute.js
import { Navigate } from "react-router-dom";
import { useSignInContext } from "../context/signinContext";
const PrivateRoute = ({children}) => {
const [signIn] = useSignInContext();
return(
<>
{signIn ? children : (<Navigate to="/signin" />)}
</>
);
}
export default PrivateRoute;
{signIn ? children : (<Navigate to=”/signin” />)}
「signIn」がtrue、つまりサインイン状態の時には、<PrivateRoute>に続くコンポーネントを表示する(このプログラムの場合は、<Home />)。
「signIn」がfalse、つまりサインアウト状態の時には、<Navigate to=”/signin” />から、signIn画面を表示するようになっています。
PublicRoute.js:”/”のパス以外が指定されたときに、パスの振り分けを行うモジュール
“/”以外のパスが指定されたときApp.jsから展開される「PublicRoute」コンポーネントのコードです。
// src/components/PublicRoute.js
import { Navigate, Outlet } from "react-router-dom";
import { useSignInContext } from "../context/signinContext";
const PublicRoute = ({children}) => {
const [signIn] = useSignInContext();
return(
<>
{signIn ? (<Navigate to="/"/>) : <Outlet />}
</>
);
}
export default PublicRoute;
{signIn ? (<Navigate to=”/”/>) : <Outlet />}
「signIn」がtrue、つまりサインイン状態の時には、何かのパスが指定されていても<Navigate>を経由で”/”パス(つまりHome画面)に遷移します。
「signIn」がfalse、つまりサインアウト状態の時には、<Outlet>を経由して、App.jsの<Route element={<PublicRoute />}>の配下に指定した、”/signup”、”/signin”、404それぞれの画面に遷移します。
以上の処理によって、サインイン中なら、URLにsigninを指定されても、ホーム画面に遷移してセキュリティを守ります。

いい加減なパスが指定されても、サインイン状態の時は、ホーム画面が表示されます。子とサンプルプロブラムの場合は、それでいいと思っています。
ただ、ちゃんとした機能のあるアプリの場合には、サインインしていてもいい加減なパスが指定されたら、404画面を出すべきでしょうね。
最後に
このブログに掲載されているコードは、著作権者(私)に帰属します。コードを自由に使用、複製、改変、配布、販売、サブライセンスできます。また、コードを使用した結果、何らかの損害が発生した場合でも、著作権者は一切責任を負いません。