背景
- 認証状態をキャッシュで保持しており、そのデータを基に
useContext
を用いて認証状態を確認 - リロード時に認証状態を確認する前に、認証前のデータが描画されてしまう
本記事の目的
- useContextを用いて認証状態が確定するまでローディングする
準備
- useContextを用いて、状態の保持のための記述を行う
- firebaseのAuthenticationを用いて認証状態の管理を行う
(※ firebaseの認証についての詳しい解説はしないので、大枠を掴んでください)
AuthContextの実装
AuthContext.ts
import { createContext } from "react";
import firebase from "firebase/auth";
interface AuthContextProps {
isAuthenticated: boolean;
login: (email: string, password: string) => Promise<void>;
logout: () => Promise<void>;
user: firebase.User | null;
loading: boolean;
}
export const AuthContext = createContext<AuthContextProps | undefined>(
undefined
);
AuthProvider.tsxの実装
AuthProvider.tsx
import { useState, ReactNode, useEffect, useMemo } from "react";
import firebase from "firebase/auth";
import { FirebaseAuth } from "@/auth/auth";
import { AuthContext } from "@/contexts/AuthContext";
interface AuthProviderProps {
children: ReactNode;
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const firebaseAuth = useMemo(() => new FirebaseAuth(), []);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [user, setUser] = useState<firebase.User | null>(null);
//初期状態ではloadingをtrueに設定
const [loading, setLoading] = useState(true);
useEffect(() => {
const unsubscribe = firebaseAuth.auth.onAuthStateChanged((user) => {
if (user) {
setIsAuthenticated(true);
setUser(user);
} else {
setIsAuthenticated(false);
setUser(null);
}
//認証状態の確認が終了したらローディングをfalseに設定
setLoading(false);
});
return () => unsubscribe();
}, [firebaseAuth]);
const login = async (email: string, password: string) => {
await firebaseAuth.login(email, password);
};
const logout = async () => {
await firebaseAuth.logout();
};
return (
<AuthContext.Provider
value={{ isAuthenticated, login, logout, user, loading }}
>
{children}
</AuthContext.Provider>
);
};
useAuth.tsにてAuthContextを使用
- 定義したAuthContextを用いて、useAuthを実装
useAuth.ts
import { useContext } from "react";
import { AuthContext } from "@/contexts/AuthContext";
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) {
throw new Error("useAuth must be used within an AuthProvider");
}
return context;
};
App.tsxにてAuthProvider
コンポーネントを使用
App.tsx
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import { AuthProvider } from "@/contexts/AuthProvider";
import "@/assets/styles/destyle.css";
import Header from "@/components/Header";
function App() {
return (
<div className="flex min-h-screen flex-col">
//AuthProviderを呼び出す
<AuthProvider>
<Router>
<Header />
<Routes>
//任意のルートを設定してください
</Routes>
</Router>
</AuthProvider>
</div>
);
}
export default App;
実装
- 処理内容
- 認証状態に応じて表示させるボタンを変化させる
- 認証状態が確定するまではボタンを描画させない
Header.tsx
import { Link, useNavigate } from "react-router-dom";
import { useAuth } from "@/contexts/useAuth";
import Button from "@/components/Button";
import { PATHS } from "@/utils/Paths";
const Header = () => {
const { isAuthenticated, logout, loading } = useAuth();
const navigate = useNavigate();
return (
<header className="sticky top-0 flex h-16 justify-between border-b-2 border-slate-300 bg-white p-3 shadow-md">
<Link
to={isAuthenticated ? PATHS.TOP : PATHS.HOME}
className="flex flex-col justify-center text-xl font-bold"
>
economEye👀
</Link>
<div className="flex gap-2">
//ローディング中は描画をしない。
//終了したら描画する
{loading ? (
<></>
) : isAuthenticated ? (
<Button
label="ログアウト"
func={() => {
logout();
navigate(PATHS.HOME);
}}
/>
) : (
<>
<Button
label="ログイン"
func={() => {
navigate(PATHS.LOGIN);
}}
/>
<Button
label="新規登録"
func={() => {
navigate(PATHS.SIGNUP);
}}
/>
</>
)}
</div>
</header>
);
};
export default Header;
あとがき
loadingを実装するまでは、リロードをするとログイン状態なのにも関わらず、ログインボタンや新規登録ボタンが一時的に描画されていましたが、loadingを実装することで防ぐことが出来ました。今回はHeaderコンポーネントを例に出しましたが、認証状態でコンポーネントが変化するものは全てloadingを用いて処理をすれば、不要なデータを見せないで実装することが出来ます。また、認証状態だけではなく、他の状態管理においても実装したいなと考えているので、もっと機能を追加したいと考えています。(現状はuseContextを認証周りでしか使っていませんが、、)