🌟

[ReactRouter] SaaSなどでありがちな、複数企業への所属と切り替えをURLパラメータで表現する方法

2024/11/08に公開

概要

SasS や toB のウェブアプリでは、1つのアカウントが複数のグループに所属することが起こります。ここでのグループとは、企業や団体やチームなどを指します。

そして、現在どのグループでログインしているかを、URL 内の ID やハッシュ値で表現することがあるかと思います。

例: /app/12345/users (12345がID部分)

それを React Router でどのように実装するかの備忘録です。

※記事中のコードはあくまでサンプルです。実際に動かすにはいくつかの修正が必要かもしれません。

環境

この記事を執筆している時点で、各ライブラリのバージョンは以下のとおりです。

  • react: ^18.0.0
  • react-router-dom: ^6.1.0

実装

まず、アプリケーションのルーティング定義を以下のように行います。

import { createBrowserRouter } from 'react-router-dom';
import { AuthCheck } from 'path/to/component'

const router = createBrowserRouter([
    // 認証不要
    { path: 'login', element: // ...

    // 要認証
    {
        path: '',
        element: <AuthCheck />, // 親コンポーネント
        children: [
            // ここでいったん全てのリクエストを受け付ける
            { path: 'company/*', element: null },

            { path: 'company/:company_hash/users', element: null }, 
            { path: 'company/:company_hash/users/:user_id', element: null },

            // これは存在しないURLへの対策
            { path: 'company/:company_hash/*', element: null },

            // ... その他のパスたち
        ]
    }
])

とあるアカウントが企業A(ハッシュ値: xxxxx)と企業B(ハッシュ値: yyyyy)に所属しているとします。企業Aのユーザーを見る時は /company/xxxxx/users に、企業Bのユーザーを見るときは /company/yyyyy/users にアクセスすることになります。

次に、 AuthCheck コンポーネントです。

import { useState, useEffect } from 'react'
import { useLocation, useNavigate, useParams, generatePath } from 'react-router-dom';

export const AuthCheck = ({ children }) => {
    const navigate = useNavigate();
    const location = useLocation();
    const params = useParams();
    const [ok, setOk] = useState(false)

    // 認証チェックとハッシュ値の自動付与を行う
    const checkAuth = async () => {
        // 認証チェックAPIを叩く。レスポンスにはアカウント情報を含める。
        const user = await fetch('...

        // ここで未認証の場合の処理

        // 現在ログイン中の企業ハッシュ値
        const currentHash = user.company.hash

        // 所属企業のハッシュ値一覧
        const companyHashes = user.companies.map(company => company.hash)

        // URLパラメータには何でも入るので、この確認が必要
        const isValidParam = companyHashes.includes(params.company_hash)

        // ハッシュ値が変わっている
        if (isValidParam && params.company_hash !== currentHash) {
            // ここで必要に応じて、指定企業への再ログイン処理など
            return;
        }

        // URLパラメータの自動付与
        if (!isValidParam) {
            // ここの URL 組み立てはお好みの方法でOK
            const newPathname = generatePath('/company/:company_hash/*', {
              company_hash: currentHash,
              '*': location.pathname.split('/company/')[1],
            });
            const newPath = `${newPathname}${location.search}`;
            return navigate(newPath, { replace: true });
        }

        setOk(true)
    }

    useEffect(() => {
        checkAuth()
    }, [location]) // ページ遷移ごとにチェックしたい

    return !ok ? null : children
}

ハッシュ値の付与を AuthCheck で統一的に行うことで、アプリケーションの各所ではハッシュ値なしでページ遷移を指定できます。

navigate("/company/users") // /company/:company_hash/users に遷移

<Link to="/company/users/1" /> // /company/:company_hash/users/1 に遷移

また、企業の切り替えもページ遷移すればOK。

<Link to="/company/yyyyy/users">
    企業Bのユーザー一覧
</Link>

以上。
Next.js や Remix を使うとまた違った実装になりそうですが、どこかで統一的に処理した方が良いという点では、共通するものはあるかと思います。

Discussion