1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Next.js 備忘録: useContext を使った状態管理とサーバーサイドの制約に悩んだ話

Posted at

はじめに

Next.js を使っていると、サーバーサイドとクライアントサイドの違いや、それぞれの制約に引っかかることが多いです。特に、状態管理で useContext を使おうとしたとき、サーバーサイドのファイルとどう整合性を取るべきかで沼りました。この記事は、そんな状況を整理して備忘録的にまとめたものです。

開発中に直面した悩み

1: useContextHeader.tsxpage.tsxの状態を共有したい
2: サーバーサイドで metadata を使って SEO 情報を設定しているため、RootLayoutをサーバーサイドにしておきたい。

なぜ困ったのか

metadata はサーバーサイドで生成される必要があるので、RootLayoutuse client をつけられません。でも、RootLayout で全体の状態を管理しようと思って useContext を使いたい場合、useState などの React Hooks を使えないのがネックでした。

問題のコード

qiita.rb

const metadata = {
  title: 'My App',
  description: 'this is Next.js app',
};



export default function RootLayout({ children }) {
  const [state, setState] = useState('');
  const stateInfo = {
    state,
    setState,
  };
  const AppStateContext = createContext(stateInfo);


  return (
    <html>
      <AppStateContext.Provider value={AppStateContext}>
        <body>{children}</body>
      </AppStateContext.Provider>
    </html>
  );
}

しかしuse client をつけると metadata が使えなくなる。

解決策: 状態管理を分離する

結論として、状態管理は RootLayout ではなく、別のクライアントサイドのProviderコンポーネントを作成して、任せることにしました。

実装例

状態管理用の Provider を作る

qiita.rb
'use client';
import React, { createContext, ReactNode, useState } from 'react';

export const AppStateContext = createContext<ProviderContext | undefined>(
  undefined
);

type ProviderContext = {
  state: number; 
  setState: React.Dispatch<React.SetStateAction<number>>;
};

export function Provider({ children }: { children: ReactNode }) {
  const [state, setState] = useState(0);

  const stateObject = {
    state,
    setState,
  };

  return (
    <AppStateContext.Provider value={stateObject}>
      {children}
    </AppStateContext.Provider>
  );
}
export default Provider;

RootLayout に組み込む

qiita.rb
import { AppStateProvider } from './Provider';

export const metadata = {
  title: 'My App',
  description: 'A simple Next.js app',
};

export default function RootLayout({ children }) {
  return (
    <html lang='en'>
      <Provider>
        <body
          className={`${geistSans.variable} ${geistMono.variable} antialiased`}
        >
          <Header />
          {children}
        </body>
      </Provider>
    </html>
  );
}

Page での利用例

qiita.rb
'use client';
import React, { useContext } from 'react';
import { AppStateContext } from '../Provider';

const Page = () => {
  const providedObject = useContext(AppStateContext);
  const plus = () => {
    providedObject?.setState((prev) => prev + 1);
  };
  return (
    <div>
      <h1>Aのページ</h1>
      <h1>{providedObject?.state}</h1>
      <button onClick={plus}>plus</button>;
    </div>
  );
};

export default Page;

Header での利用例

qiita.rb
'use client';
import React, { useContext } from 'react';
import { AppStateContext } from './Provider';

const Header = () => {
  const providedObject = useContext(AppStateContext);
  const plus = () => {
    providedObject?.setState((prev) => prev + 1);
  };
  return (
    <>
      <h1>{providedObject?.state}</h1>
      <button onClick={plus}>plus</button>;
    </>
  );
};

export default Header;

結果

Header.tsxpage.tsx の両方から state を操作でき、どちらの操作もリアルタイムに反映されました。

まとめ

専用の Provider を作ることで、状態管理を分離し、サーバーサイドとクライアントサイドの制約を回避できた

終わりに

この記事は、私自身の学習過程での気づきを整理した備忘録です。
もし内容に誤りや補足すべき点があれば、ぜひコメントで教えていただけると幸いです。

参考記事

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?