Reactプロジェクトでreact-router-domを使ってSignIn/SignUp/Home画面を制御する。

サインイン画面 プログラミング

当該記事のサンプルコードはこちら(Github)。

アプリはこちら(Outlet-Test)

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のコンポーネントを作りましょう。

Reactプロジェクトのファイル構成
// 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;
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;
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;
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 />を利用して画面のセキュリティを保つ

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画面を出すべきでしょうね。

最後に

このブログに掲載されているコードは、著作権者(私)に帰属します。コードを自由に使用、複製、改変、配布、販売、サブライセンスできます。また、コードを使用した結果、何らかの損害が発生した場合でも、著作権者は一切責任を負いません。

タイトルとURLをコピーしました