Next.js 13でのステート管理にReact Contextを使用する方法

Next.jsはステート管理にいくつかのアプローチを提供しています。これらのメソッドのいくつかは新しいライブラリのインストールを必要としますが、ReactのContext APIは組み込みなので、外部依存関係を削減するのに最適な方法です。

React Contextを使用すると、コンポーネントツリーのさまざまな部分にデータをシームレスに渡すことができ、小道具の穴埋めの手間を省くことができます。これは、現在のユーザーのログインステータスや優先テーマなど、グローバルステートを管理する場合に特に便利です。

React Context APIを理解する

コードを掘り下げる前に、React Context APIとは何か、どのような問題を解決するのかを理解することが重要です。

小道具は、コンポーネント間でデータを共有するための効果的な方法を提供します。親コンポーネントから子コンポーネントにデータを渡すことができます。

このアプローチは、どのコンポーネントが特定のデータを使用しているか、およびそのデータがコンポーネントツリーをどのように流れているかを明確に示すため、便利です。

ただし、同じ小道具を消費する必要がある深くネストされたコンポーネントがある場合に問題が発生します。この状況は複雑さを引き起こし、結果的にメンテナンスが困難な複雑なコードになる可能性があります。これらの問題は、特に小道具の穴埋めの欠点です。

React Contextは、コンポーネント全体でグローバルにアクセスする必要があるデータを作成および使用する集中型メソッドを提供することで、この課題を解決します。

このデータを入れるコンテキストを設定し、コンポーネントがアクセスできるようにします。このアプローチは、コードベースを整理して、整理されていることを確認するのに役立ちます。

このプロジェクトのコードはGitHubリポジトリにあります。

React Context APIを使用したNext.js 13でのステート管理の開始

Next.js Server Componentsを使用すると、クライアント側アプリのインタラクティブさとサーバーレンダリングのパフォーマンスの両方の利点を生かしたアプリケーションを作成できます。

Next.js 13は、デフォルトで安定しているappディレクトリにServer Componentsを実装します。ただし、すべてのコンポーネントがサーバーレンダリングされるため、React Contextなどのクライアント側ライブラリまたはAPIを統合するときに問題が発生する可能性があります。

これを回避するには、クライアント側コードを実行するファイルに設定できるuse clientフラグが最適な回避策です。

開始するには、ターミナルでこのコマンドを実行して、ローカルにNext.js 13プロジェクトを作成します。

npx create-next-app@latest next-context-api

プロジェクトを作成したら、そのディレクトリに移動します。

cd next-context-api

次に、開発サーバーを起動します。

npm run dev

基本的なNext.jsプロジェクトをセットアップしたら、ステート管理にReact Context APIを利用する基本的なTODOアプリを構築できます。

コンテキストプロバイダーの作成

コンテキストプロバイダーファイルは、コンポーネントがアクセスする必要があるグローバルステートを定義および管理する中心的なハブとして機能します。

新しいファイルsrc/context/Todo.context.jsを作成し、次のコードを入力します。

"use client"
import React, { createContext, useReducer } from "react";
const initialState = {
todos: [],
};
const reducer = (state, action) => {
switch (action.type) {
case "ADD_TODO":
return { ...state, todos: [...state.todos, action.payload] };
case "DELETE_TODO":
return { ...state, todos: state.todos.filter((todo, index) =>
index !== action.payload) };
case "EDIT_TODO":
const updatedTodos = state.todos.map((todo, index) =>
index === action.payload.index ? action.payload.newTodo : todo);
return { ...state, todos: updatedTodos };
default:
return state;
}
};
export const TodoContext = createContext({
state: initialState,
dispatch: () => null,
});
export const TodoContextProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<TodoContext.Provider value={{ state, dispatch }}>
{children}
</TodoContext.Provider>
);
};

このReact ContextセットアップはTodoContextを定義します。これは、アプリの空のTODOリストのステートを最初に保持します。

このコンテキスト構成は、初期ステートを作成することに加えて、コンテキストのステートを変更するさまざまなアクションタイプを定義するreducer関数を含みます。これらのアクションタイプは、トリガーされたアクションに応じてコンテキストのステートを変更します。この場合、アクションにはTODOの追加、削除、編集が含まれます。

TodoContextProviderコンポーネントは、アプリケーション内の他のコンポーネントにTodoContextを提供します。このコンポーネントは、コンテキストの初期ステートであるvalueプロップと、reducer関数のreducerプロップの2つのプロップを受け取ります。

コンポーネントがTodoContextを消費すると、コンテキストのステートにアクセスしてアクションをディスパッチしてステートを更新できます。

Next.jsアプリにコンテキストプロバイダーを追加する

次に、コンテキストプロバイダーがNext.jsアプリケーションのルートでレンダリングされ、すべてのクライアントコンポーネントがアクセスできるようにするために、コンテキストをアプリのルートレイアウトコンポーネントに追加する必要があります。

これを行うには、src/app/layout.jsファイルを開き、HTMLテンプレートのchildrenノードをコンテキストプロバイダーで次のようにラップします。

import './globals.css';
import { TodoContextProvider } from "@/context/Todo.context";
export const metadata = {
title: "Create Next App",
description: "Generated by create next app",
};
export default function RootLayout({
children
}) {
return (
<html lang="en">
<body>
<TodoContextProvider>{children}</TodoContextProvider>
</body>
</html>
);
}

ToDoコンポーネントを作成する

新しいファイルsrc/components/Todo.jsを作成し、次のコードを追加します。

まず、次のインポートを行います。このコンポーネントをクライアント側コンポーネントとしてマークするために、use clientフラグを必ず含めてください。

"use client"
import { TodoContext } from "@/context/Todo.context";
import React, { useContext, useState } from "react";

次に、ブラウザにレンダリングされるJSX要素を含む関数コンポーネントを定義します。

export default function Todo() {
return (
<div style={{ marginBottom: "4rem", textAlign: "center" }}>
<h2>Todos</h2>
<input
type="text"
value={todoText}
onChange={(e) => setTodoText(e.target.value)}
style={{ marginBottom: 16}}
placeholder="Enter a todo"
/>
<button onClick={handleAddTodo}>Add Todo</button>
<ul>
{state.todos.map((todo, index) => (
<li key={index}>
{index === editingIndex ? (
<>
<input
type="text"
value={editedTodo}
onChange={(e) => setEditedTodo(e.target.value)}
/>
<button
style={{ marginRight: 16}}
onClick={() => handleEditTodo(index, editedTodo)}
>
Save
</button>
</>
) : (
<>
{todo}
<button
style={{ marginRight: 16}}
onClick={() => setEditingIndex(index)}
>Edit</button>
<button
onClick={() => handleDeleteTodo(index)}
>Delete</button>
</>
)}
</li>
))}
</ul>
</div>
);
}

この関数コンポーネントには、追加、編集、削除のToDo用の入力フィールドと、対応するボタンが含まれています。編集インデックスの値に基づいて、編集ボタンと削除ボタンを表示するためにReactの条件付きレンダリングを使用します。

最後に、必要なステート変数と、各アクションタイプの必要なハンドラ関数を定義します。関数コンポーネント内で、次のコードを追加します。

const { state, dispatch } = useContext(TodoContext);
const [todoText, setTodoText] = useState("");
const [editingIndex, setEditingIndex] = useState(-1);
const [editedTodo, setEditedTodo] = useState("");
const handleAddTodo = () => {
if (todoText.trim() !== "") {
dispatch({ type: "ADD_TODO", payload: todoText });
setTodoText("");
}
};
const handleDeleteTodo = (index) => {
dispatch({ type: "DELETE_TODO", payload: index });
};
const handleEditTodo = (index, newTodo) => {
dispatch({ type: "EDIT_TODO", payload: { index, newTodo } });
setEditingIndex(-1);
setEditedTodo("");
};

これらのハンドラ関数は、コンテキストのステート内でユーザーのToDoの追加、削除、編集を処理します。

ユーザーがToDoを追加、削除、または編集すると、適切なアクションがコンテキストのreducerにディスパッチされ、それに応じてステートが更新されます。

ToDoコンポーネントをレンダリングする

最後に、ToDoコンポーネントをページコンポーネントにインポートします。

それを行うには、src/appディレクトリのpage.jsファイルを開き、Next.jsの定型コードを削除し、以下のコードを追加します。

import styles from './page.module.css'
import Todo from '../components/Todo'
export default function Home() {
return (
<main className={styles.main}>
<Todo />
</main>
)
}

これで、React Contextを使用して、ToDo Next.jsアプリのステートを管理できるようになりました。

React Context APIを他のステート管理テクノロジーと併用する

React Context APIは、ステート管理に適したソリューションです。それにもかかわらず、Reduxなどの他のステート管理ライブラリと併用することも可能です。このハイブリッドアプローチにより、重要な役割を果たすアプリのさまざまな部分に最適なツールを使用できます。

そうすることで、さまざまなステート管理ソリューションの利点を活用して、効率的で保守しやすいアプリケーションを作成できます。