【Next.js (App Router)】useContextを使用してコンポーネント間のデータ共有を行う

2024/04/11に公開

はじめに

今回はuseContextを使用したコンポーネント間のデータ共有について記述します。
Next.jsはApp Routerの構成、TypeScriptで記述しています。

useContextとは

Reactのフックの一つで、コンテキスト(Context)システムを使用してコンポーネント間でデータを共有するために使います。このフックを使用することで、プロップドリリング(親コンポーネントから子コンポーネントへプロップスを何層にもわたって手渡すこと)を避けることができ、コンポーネントツリーの任意の深さにあるコンポーネント間で状態や関数を直接共有することが可能になります。
https://ja.react.dev/reference/react/useContext
https://commte.net/nextjs-usecontext

Context、Providerコンポーネントの作成

NameProviderコンポーネントを使用して、アプリケーションのトップレベルまたは必要な範囲でラップします。これにより、その範囲内の任意のコンポーネントでuseNameフックを使用して、nameの値にアクセスしたり、setName関数を使用してその値を更新したりできるようになります。

下記は、ReactのコンテキストAPIとフックを用いて、nameという状態を共有および更新するための記述です。

web/src/app/components/contexts/nameContext.tsx
'use client';

import {
  Dispatch,
  ReactNode,
  SetStateAction,
  createContext,
  useContext,
  useState,
} from 'react';

interface NameContextType {
  name: string;
  setName: Dispatch<SetStateAction<string>>;
}

interface NameProviderProps {
  children: ReactNode;
}

// Contextの作成
const NameContext = createContext<NameContextType>({
  name: '',
  setName: () => {},
});

// Providerコンポーネントの作成
export const NameProvider = ({ children }: NameProviderProps) => {
  const [name, setName] = useState('');

  return (
    <NameContext.Provider value={{ name, setName }}>
      {children}
    </NameContext.Provider>
  );
};

// コンテキストを使用するためのカスタムフック
export const useName = () => useContext(NameContext);

Next.jsアプリケーションの全ページでデータ共有を行う場合

アプリケーションのトップレベルで使用する場合には、layout.tsxに記述してください。
その他の場合は、必要な範囲でラップしてください。

src/app/layout.tsx
import type { Metadata } from 'next';
import Footer from './components/footer/footer';
import Header from './components/header/header';
import { NameProvider } from './components/contexts/nameContext';

import './globals.css';

export const metadata: Metadata = {
  title: 'タイトル',
  description: '説明',
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {

  return (
    <html lang='ja'>
      <head>
        <meta charSet='utf-8' />
        <meta name='description' content='' />
        <meta name='keywords' content='' />
        <meta name='viewport' content='width=device-width,initial-scale=1.0' />
        <meta
          name='format-detection'
          content='telephone=no,email=no,address=no'
        />
        <link rel='canonical' href='' />
        <link rel='icon' href='/icon.ico' sizes='any' />
      </head>
      <body>
          <Header />
          <main>
            <KeyProvider>
             {children}
            </KeyProvider>
          </main>
          <Footer />
      </body>
    </html>
  );
}

使用例

const { name, setName } = useName();という記述は、カスタムフックuseNameからnamesetNameの両方を取り出していますが、どちらか一方だけが必要な場合は、必要な方だけを取り出して使用することができます。

例えば、name状態の読み取りのみで使う場合は、
const { name } = useName();と記述し、その値をコンポーネント内で利用できます。

同様に、状態の更新のみが必要で、現在の状態の値を直接参照する必要がない場合には、
const { setName } = useName();と記述して、setName関数だけを取り出して使うことができます。

src/app/page.tsx
import { useName } from './contexts/components/nameContext';

export default function Home() {
const { name, setName } = useName();
  return (
    <div>
      <p>現在の名前:{name}</p>
      <button onClick={() => setName('新しい名前')}>名前を更新</button>
    </div>
  );
}

その他のContext、Providerコンポーネントの例

'use client';

import { DataType } from '@/types/data';
import {
  Dispatch,
  ReactNode,
  SetStateAction,
  createContext,
  useContext,
  useState,
} from 'react';

interface DataContextType {
  data: {
    id: string;
    data: DataType[];
  };
  setData: Dispatch<
    SetStateAction<{
      id: string;
      data: DataType[];
    }>
  >;
}

interface DataProviderProps {
  children: ReactNode;
}

// Contextの作成
const DataContext = createContext<DataContextType>({
  data: {
    id: '',
    data: [],
  },
  setData: () => {},
});

// Providerコンポーネントの作成
export const DataProvider = ({ children }: DataProviderProps) => {
  const [data, setData] = useState<{
    id: string;
    data: DataType[];
  }>({
    id: '',
    data: [],
  });

  return (
    <DataContext.Provider value={{ data, setData }}>
      {children}
    </DataContext.Provider>
  );
};

// コンテキストを使用するためのカスタムフック
export const useData = () => useContext(DataContext);

終わりに

何かありましたらお気軽にコメント等いただけると助かります。
ここまでお読みいただきありがとうございます🎉

Discussion