Contextを使用する際のProviderの指定を間違うと共有したはずのコンテキストが子コンポーネント毎に分岐してしまうので注意が必要です。
サンプルコード
以下のようなContextを使って子コンポーネントにログイン情報を共有するコードを例にします。
//Contextファイル // AuthContext.js import React, { createContext, useState } from "react"; export const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [isLoggedIn, setIsLoggedIn] = useState(false); const toggleLogin = () => { setIsLoggedIn((prev) => !prev); }; return ( <AuthContext.Provider value={{ isLoggedIn, toggleLogin }}> {children} </AuthContext.Provider> ); };
//子コンポーネント1 //ComponentA.jsx import React, { useContext } from "react"; import { AuthContext } from "./AuthContext"; const ComponentA = () => { const { isLoggedIn, toggleLogin } = useContext(AuthContext); return ( <div> <h2>ComponentA</h2> <p>ログイン状態: {isLoggedIn ? "ログイン済み" : "未ログイン"}</p> <button onClick={toggleLogin}> {isLoggedIn ? "ログアウト" : "ログイン"} </button> </div> ); }; export default ComponentA;
//子コンポーネント2 //ComponentB.jsx import React, { useContext } from "react"; import { AuthContext } from "./AuthContext"; const ComponentB = () => { const { isLoggedIn, toggleLogin } = useContext(AuthContext); return ( <div> <h2>ComponentB</h2> <p>ログイン状態: {isLoggedIn ? "ログイン済み" : "未ログイン"}</p> <button onClick={toggleLogin}> {isLoggedIn ? "ログアウト" : "ログイン"} </button> </div> ); }; export default ComponentB;
サンプルなので子コンポーネントComponentA
、ComponentB
の中身は同じです。
コンテキストが分岐してしまう書き方
以下のような場合は子コンポーネント毎に別々のコンテキストを持つ(分岐してしまう)ことになる。
//親コンポーネント //App.js //誤: コンテキストが分岐する import React from "react"; import { AuthProvider } from "./AuthContext"; const App = () => { return ( <div> {/* 子コンポーネントが同じProviderを別々に囲う */} <AuthProvider> <ComponentA /> </AuthProvider> <AuthProvider> <ComponentB /> </AuthProvider> </div> ); };
isLoggedIn
stateが子コンポーネント間で別々のstateとして扱われる- 子コンポーネント内のログインボタンで
isLoggedIn
stateを更新してもそれぞれの子コンポーネント内のstateしか更新されない- 例1) ComponentAでログインボタンクリック → ComponentAの
isLoggedIn
stateしか更新されない - 例2) ComponentBでログインボタンクリック → ComponentBの
isLoggedIn
stateしか更新されない
- 例1) ComponentAでログインボタンクリック → ComponentAの
結果的にログイン状態を表示するログイン済み/未ログイン
の文字の切替、ログインボタンのログイン/ログアウト
の文字の切替が子コンポーネント毎にしか動作しなくなります。
コンテキストを正しく共有する書き方
以下のようにコンテクストを共有したいコンポーネントを1つのProvideで囲むことでコンテキストが正しく共有されます。
//親コンポーネント //App.js //正: コンテキストが正しく共有される import React from "react"; import { AuthProvider } from "./AuthContext"; const App = () => { return ( <div> {/* 2つの子コンポーネントを1つのProviderで囲う */} <AuthProvider> <ComponentA /> <ComponentB /> </AuthProvider> </div> ); };
- 例1) ComponentAでログインボタンクリック → ComponentA、ComponentBの
isLoggedIn
stateが更新される - 例2) ComponentBでログインボタンクリック → ComponentA、ComponentBの
isLoggedIn
stateが更新される
ログイン状態を表すログイン済み/未ログイン
の文字、ログインボタンのログイン/ログアウト
の文字がどちらの子コンポーネントでも切り替わるようになります。
まとめ
- Contextを使ってコンテキストが共有されない場合はProviderが正しく設定されているか疑う
- 同じコンテキストを共有する複数の子コンポーネントがある場合、1つの親コンポーネント内で1つのProviderで囲むこと
- 複数のProviderを使用する場合、それぞれが異なる状態を持つ
感想
React、便利なんだろうけど慣れるまでは修羅の道... はやく楽しく触れるようになりたい。