【Next.js (App Router)】useContextを使用してコンポーネント間のデータ共有を行う
はじめに
今回はuseContext
を使用したコンポーネント間のデータ共有について記述します。
Next.jsはApp Routerの構成、TypeScriptで記述しています。
useContextとは
Reactのフックの一つで、コンテキスト(Context)システムを使用してコンポーネント間でデータを共有するために使います。このフックを使用することで、プロップドリリング(親コンポーネントから子コンポーネントへプロップスを何層にもわたって手渡すこと)を避けることができ、コンポーネントツリーの任意の深さにあるコンポーネント間で状態や関数を直接共有することが可能になります。
Context、Providerコンポーネントの作成
NameProvider
コンポーネントを使用して、アプリケーションのトップレベルまたは必要な範囲でラップします。これにより、その範囲内の任意のコンポーネントでuseName
フックを使用して、name
の値にアクセスしたり、setName
関数を使用してその値を更新したりできるようになります。
下記は、ReactのコンテキストAPIとフックを用いて、name
という状態を共有および更新するための記述です。
'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
に記述してください。
その他の場合は、必要な範囲でラップしてください。
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
からname
とsetName
の両方を取り出していますが、どちらか一方だけが必要な場合は、必要な方だけを取り出して使用することができます。
例えば、name
状態の読み取りのみで使う場合は、
const { name } = useName();
と記述し、その値をコンポーネント内で利用できます。
同様に、状態の更新のみが必要で、現在の状態の値を直接参照する必要がない場合には、
const { setName } = useName();
と記述して、setName
関数だけを取り出して使うことができます。
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