React コンテキスト

概要: このチュートリアルでは、React コンテキストについて、そしてReact アプリ全体で状態を共有するためにどのように使用するかを学習します。

React コンテキスト入門

React コンテキストは、React の機能であり、コンポーネントツリーのすべてのレベルにpropsを渡すことなく、React アプリ全体(またはその一部)で状態を共有することを可能にします。

React コンテキストは、異なるネストレベルの多くのコンポーネントが状態の一部にアクセスする必要がある場合に役立ちます。

React コンテキストを実装するためのステップバイステップガイドを以下に示します。

ステップ 1. コンテキストを作成する

まず、react ライブラリの createContext() 関数を使用してコンテキストを作成します。

// MyContext.js

import { createContext } fronm 'react';

const MyContext = createContext();Code language: JavaScript (javascript)

ステップ 2. プロバイダーコンポーネントを作成する

プロバイダーコンポーネントは、コンテキストオブジェクトを使用して、共有状態を子コンポーネントに提供します。

// MyProvider.js
import { useState } from 'react';
import MyContext from './MyContext';

const MyProvider = ({ children }) => {
  const [state, setState] = useState();
  
  const shared = {state, setState };

  return (
    <MyContext.Provider value={shared}>
      {children}
    </MyContext.Provider>
  );
};

export default MyProvider;Code language: JavaScript (javascript)

この例では、Provider コンポーネントは、statesetState 関数を含むオブジェクト(shared)を子コンポーネントに共有します。

statesetState 関数に加えて、共有オブジェクトに配置することで任意の関数を共有できます。

ステップ 3. アプリでプロバイダーコンポーネントを使用する

プロバイダーコンポーネント内でコンポーネントツリーをラップして、すべての子コンポーネントがコンテキストオブジェクトにアクセスできるようにします。

// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import MyProvider from './MyProvider';

const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);

root.render(
  <MyProvider>
    <App />
  </MyProvider>
);Code language: JavaScript (javascript)

この例では、App コンポーネントとそのすべての子コンポーネントは、Provider コンポーネントによって共有される MyContext オブジェクトにアクセスできます。

ステップ 4. コンポーネントでコンテキストを使用する

Provider コンポーネントの子コンポーネントから MyContext オブジェクトを使用(またはアクセス)する方法を以下に示します。

import { useContext } from 'react';
import MyContext from './MyContext';

const MyComponent = () => {
  const { state, setState } = useContext(MyContext);

  return (
    <div>
      <p>{state.someValue}</p>
      <button onClick={() => setState({ ...state, someValue: 'New Value' })}>
        Update Value
      </button>
    </div>
  );
};

export default MyComponent;Code language: JavaScript (javascript)

コンテキストを使用して Todo アプリをリファクタリングする

Todo アプリをリファクタリングして、React コンテキストを使用してみましょう。

React Context

この図では

  • App コンポーネントは、コンテキストから getTodos 関数にアクセスして、すべての todo アイテムを取得します。
  • TodoList コンポーネントは、todos 状態にアクセスして、すべての todo アイテムを表示します。
  • TodoShow コンポーネントは、changeTodo 関数と removeTodo 関数にアクセスします。
  • TodoCreate コンポーネントは、createTodo 関数にアクセスして、新しい todo アイテムを作成します。

1) React コンテキストの作成

まず、src ディレクトリ内に context という新しいディレクトリを作成します。

次に、コンテキストオブジェクトと Provider コンポーネントを格納する todos.js ファイルを作成します。

import { createContext, useState  } from 'react';

const TodosContext = createContext();

const Provider = ({ children }) => {
  const [todos, setTodos] = useState([]);

  const getTodos = async () => {
    const url = 'http://localhost:5000/todos';
    try {
      const response = await fetch(url, {
        method: 'GET',
        headers: {
          'Content-Type': 'application/json',
        },
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }

      const storedTodos = await response.json();
      // Update the state
      if (storedTodos) setTodos(storedTodos);
    } catch (error) {
      console.error('Error during GET request:', error);
    }
  };

  const createTodo = async (title) => {
    // call an API to create a new todo
    const url = 'http://localhost:5000/todos';
    try {
      const response = await fetch(url, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          title: title,
          completed: false,
        }),
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      const responseData = await response.json();

      const newTodo = { ...responseData };

      // add the new todo to the list
      const updatedTodos = [...todos, newTodo];
      setTodos(updatedTodos);
    } catch (error) {
      console.error('Error creating a todo:', error);
    }
  };

  const removeTodo = async (id) => {
    // Delete the todo
    const url = `http://localhost:5000/todos/${id}`;

    try {
      const response = await fetch(url, {
        method: 'DELETE',
        headers: {
          'Content-Type': 'application/json',
        },
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }

      // Update the state
      const updatedTodos = todos.filter((todo) => todo.id !== id);
      setTodos(updatedTodos);
    } catch (error) {
      console.error('Error during DELETE request:', error);
      throw error;
    }
  };

  const changeTodo = async (id, newTitle, completed = false) => {
    // Update todo
    const url = `http://localhost:5000/todos/${id}`;

    const data = { title: newTitle, completed };

    try {
      const response = await fetch(url, {
        method: 'PUT',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(data),
      });

      if (!response.ok) {
        throw new Error('Network response was not ok');
      }

      const responseData = await response.json();

      // Update the state
      const updatedTodos = todos.map((todo) => {
        if (todo.id === id) {
          return { ...todo, ...responseData };
        }
        return todo;
      });
      setTodos(updatedTodos);
    } catch (error) {
      console.error('Error during PUT request:', error);
      throw error;
    }
  };

  const shared = { todos, getTodos, createTodo, removeTodo, changeTodo };

  return (
    <TodosContext.Provider value={shared}>{children}</TodosContext.Provider>
  );
};

export default TodosContext;

export { Provider };Code language: JavaScript (javascript)

仕組み。

まず、react ライブラリから createContext 関数と useState 関数をインポートします。

import { createContext, useState } from 'react';Code language: JavaScript (javascript)

次に、createContext 関数を使用して、TodosContext という新しい Context オブジェクトを作成します。

const TodosContext = createContext();Code language: JavaScript (javascript)

3 番目に、Provider コンポーネントを定義します。

const Provider = ({ children }) => {
   // ...
}Code language: JavaScript (javascript)

4 番目に、`useState` 関数を使用して Provider コンポーネントの状態を作成します。

const [todos, setTodos] = useState([]);Code language: JavaScript (javascript)

5 番目に、getTodoscreateTodoremoveTodo、および changeTodo 関数を App コンポーネントから Provider コンポーネントに移動します。

6 番目に、コンポーネント間で共有される状態と関数を含むオブジェクトを定義します。

 const shared = { todos, getTodos, createTodo, removeTodo, changeTodo };Code language: JavaScript (javascript)

7 番目に、共有オブジェクトを Provider コンポーネントの value prop に渡して、Provider コンポーネントを返します。

return (
   <TodosContext.Provider value={shared}>{children}</TodosContext.Provider>
);Code language: JavaScript (javascript)

最後に、デフォルトエクスポートを使用して TodosContext をエクスポートし、名前付きエクスポートを使用して Provider コンポーネントをエクスポートします。

export default TodosContext;
export { Provider };Code language: JavaScript (javascript)

Provider コンポーネントを使用する

`index.js` ファイルで`App`コンポーネントを `Provider` コンポーネントでラップするように `index.js` を変更します。

import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { Provider } from './context/todos';

const el = document.querySelector('#root');
const root = ReactDOM.createRoot(el);

root.render(
  <Provider>
    <App />
  </Provider>
);Code language: JavaScript (javascript)

仕組み。

まず、コンテキストから Provider コンポーネントをインポートします。

import { Provider } from './context/todos';Code language: JavaScript (javascript)

次に、App コンポーネントを Provider コンポーネントでラップします。

root.render(
  <Provider>
    <App />
  </Provider>
);Code language: JavaScript (javascript)

App コンポーネントとすべての子コンポーネントは、Provider コンポーネントによって提供される TodosContext オブジェクトにアクセスできるようになります。

App コンポーネントの変更

`App` コンポーネントを以下のように変更します。

import TodoList from './components/TodoList';
import TodoCreate from './components/TodoCreate';
import { useEffect, useContext } from 'react';
import TodosContext from './context/todos';
import './App.css';

const App = () => {
  const { getTodos } = useContext(TodosContext);

  useEffect(() => {
    getTodos();
  }, []);

  return (
    <main className="main">
      <h1>
        React Todo <span>Streamline Your Day, the React Way!</span>
      </h1>
      <TodoList />
      <TodoCreate />
    </main>
  );
};

export default App;
Code language: JavaScript (javascript)

仕組み。

まず、TodosContext オブジェクトをインポートします。

import TodosContext from './context/todos';Code language: JavaScript (javascript)

次に、useContext() 関数を使用して TodosContext から getTodos 関数にアクセスします。

const { getTodos } = useContext(TodosContext);Code language: JavaScript (javascript)

3 番目に、useEffect フック内で getTodos 関数を呼び出します。

useEffect(() => {
  getTodos();
}, []);Code language: JavaScript (javascript)

TodoList コンポーネントの変更

`TodosContext` を使用する `TodoList` コンポーネントを変更します。

import TodoShow from './TodoShow';
import TodosContext from '../context/todos';
import { useContext } from 'react';

const TodoList = () => {
  const { todos } = useContext(TodosContext);
  
  const renderedTodos = todos.map((todo) => {
    return <TodoShow key={todo.id} todo={todo} />;
  });

  return <ul className="todo-list">{renderedTodos}</ul>;
};

export default TodoList;Code language: JavaScript (javascript)

仕組み。

まず、TodosContext をインポートします。

import TodosContext from '../context/todos';Code language: JavaScript (javascript)

次に、useContext 関数をインポートします。

import { useContext } from 'react';Code language: JavaScript (javascript)

3 番目に、TodosContext から todos 配列にアクセスします。

const { todos } = useContext(TodosContext);Code language: JavaScript (javascript)

最後に、各 todo アイテムを `TodoShow` コンポーネントに渡します。

const renderedTodos = todos.map((todo) => {
  return <TodoShow key={todo.id} todo={todo} />;
});Code language: JavaScript (javascript)

TodoShow コンポーネントの変更

`TodosContext` を使用するように `TodoShow.js` コンポーネントを変更します。

import { useState } from 'react';
import TodoEdit from './TodoEdit';
import EditIcon from '../edit.svg';
import DeleteIcon from '../delete.svg';
import { useContext } from 'react';
import TodosContext from '../context/todos';

const TodoShow = ({ todo }) => {
  const [showEdit, setShowEdit] = useState(false);
  const { removeTodo, changeTodo } = useContext(TodosContext);

  const handleDelete = (e) => {
    removeTodo(todo.id);
  };

  const handleEdit = (e) => {
    setShowEdit(!showEdit);
  };

  const handleSubmit = (id, title) => {
    changeTodo(id, title);
    setShowEdit(false);
  };

  const handleDoubleClick = (e) => {
    e.preventDefault();
    changeTodo(todo.id, todo.title, !todo.completed);
    setShowEdit(false);
  };

  if (showEdit)
    return (
      <li className="todo">
        <TodoEdit todo={todo} onSubmit={handleSubmit} />
      </li>
    );
  else {
    return (
      <li className="todo" onDoubleClick={handleDoubleClick}>
        <p className={todo.completed ? 'completed' : 'open'}>{todo.title}</p>

        <div className="actions">
          <button onClick={handleDelete}>
            <img src={DeleteIcon} title="Delete" />
          </button>
          <button onClick={handleEdit}>
            <img src={EditIcon} title="Edit" />
          </button>
        </div>
      </li>
    );
  }
};

export default TodoShow;Code language: JavaScript (javascript)

仕組み。

まず、react ライブラリから useContext 関数を、todos.js モジュールから TodosContext オブジェクトをインポートします。

import { useContext } from 'react';
import TodosContext from '../context/todos';Code language: JavaScript (javascript)

次に、コンテキストから removeTodo 関数と changeTodo 関数にアクセスします。

const { removeTodo, changeTodo } = useContext(TodosContext);Code language: JavaScript (javascript)

TodoCreate コンポーネントの変更

`TodosContext` オブジェクトを使用するように `TodoCreate` コンポーネントを変更します。

import { useState, useContext } from 'react';
import TodosContext from '../context/todos';

const TodoCreate = () => {
  const [todo, setTodo] = useState('');
  const { createTodo } = useContext(TodosContext);

  const handleSubmit = (e) => {
    e.preventDefault();
    createTodo(todo);
    setTodo('');
  };

  const handleChange = (e) => {
    setTodo(e.target.value);
  };

  return (
    <form onSubmit={handleSubmit} className="todo-create">
      <input
        type="text"
        name="todo"
        id="todo"
        placeholder="Enter a todo"
        value={todo}
        onChange={handleChange}
      />
    </form>
  );
};

export default TodoCreate;Code language: JavaScript (javascript)

仕組み。

まず、TodosContext オブジェクトをインポートします。

import TodosContext from './context/todos';Code language: JavaScript (javascript)

次に、useContext() 関数を使用して TodosContext から createTodo 関数にアクセスします。

const { createTodo } = useContext(TodosContext);Code language: JavaScript (javascript)

3 番目に、createTodo 関数を呼び出して、新しい todo アイテムを作成します。

const handleSubmit = (e) => {
  e.preventDefault();
  createTodo(todo);
  setTodo('');
};Code language: JavaScript (javascript)

プロジェクトのソースコードをダウンロードする

コンテキストを使用した React Todo アプリをダウンロードする

まとめ

  • React アプリ全体で状態を共有するには、React コンテキストを使用します。
このチュートリアルは役に立ちましたか?