以前作成したメモアプリに「ログインボタン」を実装し、グローバルなstateを扱う最もシンプルな方法として、React の機能であるContext(コンテクスト)を学びました。
概説
useState()
によって管理される値はローカルなstateと呼ばれ、その値は特定のコンポーネント内でしか利用できない。
しかし、複数のコンポーネントからその値を参照したい場合、props
を大量に渡す必要が出てくる。このような状況を避けるために、グローバルな状態管理が有用。
Contextとは
コンポーネントツリー全体でデータを共有するための方法。
複数のコンポーネントにprops経由で渡す手間が省ける。
アプリケーション全体で必要とされる情報(認証情報、テーマ設定など)を扱う際に便利。
Contextの作成
以下のようにcreateContext()
を使用してContextを作成。
import { createContext } from 'react'; export const MyContext = createContext(defaultValue);
Providerの使用
Providerは、value
属性を用いて子孫コンポーネントにコンテクストの値を供給。
一方で、子孫コンポーネントはuseContext()
フックを使用してこの値にアクセスできる。
<MyContext.Provider value={/* some value */}> {/* children */} </MyContext.Provider>
今回の要件
- 未ログイン時はログインボタンを、ログイン時はログアウトボタンを表示する。
- 未ログイン時はメモの閲覧のみ可能で、ログインするとメモの追加/更新/削除ができる。
※ UI制御の練習のため、ID/PasswordやOAuth等を利用した実際の認証機能を実装はなし。
ログインボタンを押せばログイン状態になり、ログアウトボタンを押せばログアウト状態となるような「見かけ上のログイン機能」。新規アカウント作成等の機能もなし。
実装
可読性と保守性を高めるために、ReactのJSX構文を含むファイルは.jsx
であるべき。
1. ログイン状態管理用のContextとカスタムフックを作成
新規でsrc/AuthContext.jsx
を作成し、そこにログイン状態を管理するためのAuthContext
を定義。
ここでは、Custom HookのuseLogin
も作成。
import { createContext, useContext, useState } from "react"; const AuthContext = createContext(); export function AuthProvider({ children }) { const [loggedIn, setLoggedIn] = useState(false); const login = () => setLoggedIn(true); const logout = () => setLoggedIn(false); return ( <AuthContext.Provider value={{ loggedIn, login, logout }}> {children} </AuthContext.Provider> ); } export function useLogin() { return useContext(AuthContext); }
2. App.js
でログインプロバイダーを使用
src/App.js
で作成したAuthProvider
をラップすることで、全コンポーネントでログイン状態を共有できるようにする。
import { AuthProvider } from "./AuthContext.jsx"; // 追加 ... function App() { return ( <AuthProvider> {/* ラップする */} </AuthProvider> ); } export default App;
3. ログイン/ログアウトボタンの実装
ログイン/ログアウトボタンを追加。
src/components/MemoList.jsx
import { useLogin } from "../AuthContext.jsx"; // 追加 function MemoList({ memos, onMemoSelect, onMemoAdd }) { const { loggedIn, login, logout } = useLogin(); // 追加 return ( <> <div className="login-button"> <button onClick={loggedIn ? logout : login}> {loggedIn ? "ログアウト" : "ログイン"} </button> </div> {memos.map((memo) => ( <div key={memo.id} onClick={() => onMemoSelect(memo.id)} className="memo-item" > {memo.text.split("\n")[0]} </div> ))} {loggedIn && <button onClick={onMemoAdd}>+</button>} {/* ログイン時のみ表示 */} </> ); } export default MemoList;
4. ログイン時のみ特定のUIを表示
メモの編集と削除をログイン状態に応じて制御。
src/components/MemoDetail.jsx
import { useLogin } from '../AuthContext'; // 追加 function MemoDetail({ memo, onMemoSave, onMemoDelete }) { const { loggedIn } = useLogin(); // 追加 ... return ( <> <textarea value={memoText} onChange={(e) => setMemoText(e.target.value)} readOnly={!loggedIn} // ログインしていない場合は読み取り専用 /> {loggedIn && ( <> <button onClick={handleSave}>編集</button> <button className="delete-button" onClick={onMemoDelete}> 削除 </button> </> )} </> ); } export default MemoDetail;
まとめ✏️
今回は見かけ上でしたが、ログイン/ログアウト機能を割と簡単に実装できました。 Contextでアプリ全体で状態を共有できるので、コードの冗長性を大幅に削減することができました。 次のステップとしては、実際の認証機能(JWT, OAuthなど)や新規アカウント作成機能も踏まえて学習していきたいです。
参考資料
・コンテクストで深くデータを受け渡す
・useContext
・createContext
・React Context を export するのはアンチパターンではないかと考える
・カスタムフックで Context をエクスポートしないようリファクタリングする