Next.jsで独立したCCで状態を共有したい
1. はじめに
現在、Next14で標準となっているApp Routerでは、SC(Server Component)が標準であり、必要なときにのみCC(Client Component)を使用することになっています。
しかし、この構成だと状態管理が問題となる場合があります。状態を管理するには、通常useStateを使用しますが、これはCCでないと使用することができません。
CCは必要なところにのみピンポイントで使用するため、例えばヘッダーとサイドバーで状態を共有したいときに困ってしまいます(実際困りました)。
そこで、jotaiというライブラリを用いて、独立したCC同士で共通のstateを使用する方法を紹介します。
※TypeScriptは使用していないので、使用される方は適宜読み替えてください。
2. jotaiの使い方イメージ
jotaiとは、Reactで使用される状態管理ライブラリです。パッケージ名は日本語の「状態」から名付けられたそうです。
また、以下の3ステップで簡単に使用することができます。
- layout.js内の状態を共有したい箇所をProviderで囲う
- 共有したい状態をatomとして作成し、exportしておく
- その状態を使用したいCCでimportして使用する
Next.jsの触りはじめでは、親から子にpropsを渡しているイメージがどうしても先行してしまうので、独立したCC同士での変数の共有のイメージがわかないかもしれません。
しかし、状態もatomという形式にすることで、exportやimportができるようになり、グローバルな状態共有が可能となります。
それでは、実際にサンプルコードを用いて確認していきましょう。
3. サンプルコード
- 前準備
Next.jsの新しいプロジェクトを作成したら、HeaderとSidebarを作っていきます。
cssは面倒なので、省略して、jsxファイルだけ作成していきます。
まず、以下の4つのファイルを作成します。
import HeaderClient from "@/app/ui/header/header-client";
export default function Header() {
return (
<>
<HeaderClient />
</>
);
}
"use client";
export default function HeaderClient() {
return (
<>
</>
);
}
import SidebarClient from "@/app/ui/sidebar/sidebar-client";
export default function Sidebar() {
return (
<>
<SidebarClient />
</>
);
}
"use client";
export default function SidebarClient() {
return (
<>
</>
);
}
そして、これらをlayout.jsで表示できるようにします。
import Header from "@/app/ui/header/header";
import Sidebar from "@/app/ui/sidebar/sidebar";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="ja">
<body className={inter.className}>
<Header />
<Sidebar />
{children}
</body>
</html>
);
}
これでjotaiを試していく下地ができたので、早速残りの準備をしていきましょう。
- jotaiをインストール
まずは以下のコマンドでjotaiをインストールします。
npm install jotai
- Providerの作成
次に、Providerを作成します。
今回のように、アプリケーション全体で状態を共有する場合は、AppProviderみたいな名前で囲っておくと良いと思います。
"use client";
import { Provider } from "jotai";
export function AppProvider({ children }) {
return <Provider>{children}</Provider>;
}
import Header from "@/app/ui/header/header";
import Sidebar from "@/app/ui/sidebar/sidebar";
import { AppProvider } from "@/app/provider";
import { Inter } from "next/font/google";
const inter = Inter({ subsets: ["latin"] });
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({ children }) {
return (
<html lang="ja">
<body className={inter.className}>
<AppProvider>
<Header />
<Sidebar />
{children}
</AppProvider>
</body>
</html>
);
}
- Atomの作成とexport
あとは、独立したCC同士で状態を共有するために、Atomを作成する必要があります。
状態管理のメジャーなものとして、ログイン状態を管理する変数を仮定して作成してみましょう。
import { atom } from "jotai";
export const loginState = atom(false);
- Atomのimport
これで、後は使いたいCCにこのatomをimportするだけです。
HeaderClientとSidebarClientにimportしていきましょう。
"use client";
import { useAtom } from "jotai";
import { loginState } from "@/app/state/login";
export default function HeaderClient() {
const [isLogin, setIsLogin] = useAtom(loginState);
return (
<>
<button onClick={() => setIsLogin(!isLogin)}>ログイン/ログアウト</button>
<div>現在のログイン状態:{String(isLogin)}</div>
</>
);
}
"use client";
import { useAtom } from "jotai";
import { loginState } from "@/app/state/login";
export default function SidebarClient() {
const [isLogin, setIsLogin] = useAtom(loginState);
return (
<>
<button onClick={() => setIsLogin(!isLogin)}>ログイン/ログアウト</button>
<div>現在のログイン状態:{String(isLogin)}</div>
</>
);
}
4. 実際の画面
これで、HeaderとSidebarで状態を共有することができました。
CSSを反映していないので少しわかりづらいですが、以下のようにHeader領域とSidebar領域で状態が共有されていることがわかると思います。
5. まとめ
独立したCC間での状態共有を、jotaiというライブラリを使用することで、
- layout.js内の状態を共有したい箇所をProviderで囲う
- 共有したい状態をatomとして作成し、exportしておく
- その状態を使用したいCCでimportして使用する
という3ステップで、簡単に実装することができました。
6. 最後に
Next.jsでは、Server Componentが標準であることから、どうしてもReactのライブラリをそのまま適用できないことがありますが、今回のケースに関しては、無駄にCCを増やすことなくグローバルな状態共有ができたので、良かったです。
この記事が少しでも、Next.js学習中の方の参考となれば幸いです。
そして最後に、List It Bro! というTwitchクリップのプレイリストを作成・管理するサイトを現在開発しているので、良かったら試してみてください!
Discussion