layout.tsx の useState を pages.tsx から参照できるようにします
動作確認環境
Next.js 15.1.0 (App router)
layout.tsx はページ間で共通のレイアウトを設定するのにとても便利です
では、layout.tsx に定義された useState の値を pages 側から変更したくなった場合はどうすべきでしょうか
例えば、ダークモードのフラグを持たせておきたいとかです
...引数のバケツリレーも考えられますが、サーバ側のコンポーネント等を挟むと渡せないようでした
この場合は React hooks の useContext を利用すると簡単に共有できます
今回は、こんな感じの構造を想定してみます
src├── app/│ ├── layout.tsx│ └── page.tsx└── components/└── layoutBase.tsx
layout.tsx は、場合によっては Edge Runtime 等に使いたいこともあると思います
なので layoutBase.tsx にクライアントサイドの処理や useState を持たせるようにします
// layout.tsximport { LayoutBase } from '@/src/components/layoutBase'const Layout = ({ children }: { children?: React.ReactNode }) => {return (<html><head><title>Test</title></head><body><LayoutBase>{children}</LayoutBase></body></html>)}export default Layout
クライアントサイドの layoutBase.tsx はこんな感じにしてみました
ここでは useState でダークモードに関するフラグを持たせています
// layoutBase.tsxexport const LayoutBase = ({ children }: { children?: React.ReactNode }) => {const [darkMode, setDarkMode] = useState(false)return (<div style={{ backgroundColor: darkMode ? 'black' : 'white' }}>{children}</div>)}
この darkMode を page.tsx から取得できるようにします
まず createContext を使って darkMode を共有するためのコンテキストを作成します
// layoutBase.tsxexport const DarkModeContext = createContext({darkMode: false,setDarkMode: (mode: boolean) => {},})
次に、DarkModeContext.Provider を使って darkMode を共有します
// layoutBase.tsxexport const DarkModeContext = createContext<{darkMode: booleansetDarkMode: (mode: boolean) => void}>({} as any)export const LayoutBase = ({ children }: { children?: React.ReactNode }) => {const [darkMode, setDarkMode] = useState(false)return (<DarkModeContext.Provider value={{ darkMode, setDarkMode }}><div style={{ backgroundColor: darkMode ? 'black' : 'white' }}>{children}</div></DarkModeContext.Provider>)}
最後に、page.tsx で useContext を使って darkMode を取得できるようにします
ついでなので、ダークモードを切り替えるボタンも追加してみました
// page.tsx'use client'import { useContext } from 'react'import { DarkModeContext } from '@/src/components/layoutBase'export default async () => {const { darkMode, setDarkMode } = useContext(DarkModeContext)return (<div><div>Dark Mode: {darkMode ? 'ON' : 'OFF'}</div><button onClick={() => setDarkMode(!darkMode)}>Toggle Dark Mode</button></div>)}
これで、layoutBase.tsx で定義した darkMode を page.tsx から変更できるようになりました 🎉
(おまけ) useContext を使わず let で共有した場合
おわりに
useContext は、循環参照やスパゲティなプログラムになりやすいかなあと思い避けてましたが、こういう場合には使わざるをえないのかなと思います 🍝
回避方法があれば教えてくださいー 🐶