🗂

Next.jsで独立したCCで状態を共有したい

2024/04/15に公開

1. はじめに

現在、Next14で標準となっているApp Routerでは、SC(Server Component)が標準であり、必要なときにのみCC(Client Component)を使用することになっています。

しかし、この構成だと状態管理が問題となる場合があります。状態を管理するには、通常useStateを使用しますが、これはCCでないと使用することができません。

CCは必要なところにのみピンポイントで使用するため、例えばヘッダーとサイドバーで状態を共有したいときに困ってしまいます(実際困りました)。

そこで、jotaiというライブラリを用いて、独立したCC同士で共通のstateを使用する方法を紹介します。

※TypeScriptは使用していないので、使用される方は適宜読み替えてください。

2. jotaiの使い方イメージ

jotaiとは、Reactで使用される状態管理ライブラリです。パッケージ名は日本語の「状態」から名付けられたそうです。

また、以下の3ステップで簡単に使用することができます。

  1. layout.js内の状態を共有したい箇所をProviderで囲う
  2. 共有したい状態をatomとして作成し、exportしておく
  3. その状態を使用したいCCでimportして使用する

Next.jsの触りはじめでは、親から子にpropsを渡しているイメージがどうしても先行してしまうので、独立したCC同士での変数の共有のイメージがわかないかもしれません。

しかし、状態もatomという形式にすることで、exportやimportができるようになり、グローバルな状態共有が可能となります。

それでは、実際にサンプルコードを用いて確認していきましょう。

3. サンプルコード

  1. 前準備

Next.jsの新しいプロジェクトを作成したら、HeaderとSidebarを作っていきます。

cssは面倒なので、省略して、jsxファイルだけ作成していきます。

まず、以下の4つのファイルを作成します。

/app/ui/header/header.jsx
import HeaderClient from "@/app/ui/header/header-client";

export default function Header() {
  return (
    <>
      <HeaderClient />
    </>
  );
}

/app/ui/header/header-client.jsx
"use client";

export default function HeaderClient() {
  return (
    <>
    </>
  );
}

/app/ui/sidebar/sidebar.jsx
import SidebarClient from "@/app/ui/sidebar/sidebar-client";

export default function Sidebar() {
  return (
    <>
      <SidebarClient />
    </>
  );
}
/app/ui/sidebar/sidebar-client.jsx
"use client";

export default function SidebarClient() {
  return (
    <>
    </>
  );
}

そして、これらをlayout.jsで表示できるようにします。

/app/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を試していく下地ができたので、早速残りの準備をしていきましょう。

  1. jotaiをインストール

まずは以下のコマンドでjotaiをインストールします。

npm install jotai
  1. Providerの作成

次に、Providerを作成します。

今回のように、アプリケーション全体で状態を共有する場合は、AppProviderみたいな名前で囲っておくと良いと思います。

/app/provider.jsx
"use client";

import { Provider } from "jotai";

export function AppProvider({ children }) {
  return <Provider>{children}</Provider>;
}
/app/layout.js
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>
  );
}

  1. Atomの作成とexport

あとは、独立したCC同士で状態を共有するために、Atomを作成する必要があります。

状態管理のメジャーなものとして、ログイン状態を管理する変数を仮定して作成してみましょう。

/app/state/login.js
import { atom } from "jotai";

export const loginState = atom(false);
  1. Atomのimport

これで、後は使いたいCCにこのatomをimportするだけです。

HeaderClientとSidebarClientにimportしていきましょう。

/app/ui/header/header-client.jsx
"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>
    </>
  );
}

/app/ui/sidebar/sidebar-client.jsx
"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というライブラリを使用することで、

  1. layout.js内の状態を共有したい箇所をProviderで囲う
  2. 共有したい状態をatomとして作成し、exportしておく
  3. その状態を使用したいCCでimportして使用する

という3ステップで、簡単に実装することができました。

6. 最後に

Next.jsでは、Server Componentが標準であることから、どうしてもReactのライブラリをそのまま適用できないことがありますが、今回のケースに関しては、無駄にCCを増やすことなくグローバルな状態共有ができたので、良かったです。

この記事が少しでも、Next.js学習中の方の参考となれば幸いです。

そして最後に、List It Bro! というTwitchクリップのプレイリストを作成・管理するサイトを現在開発しているので、良かったら試してみてください!

Discussion