ReactのuseContextはコンポーネント間でのデータの受け渡しに関するHookです。useStateやuseReduceと合わせて利用することができます。まず本書では最も基本的なuseContextの使用方法について説明を行いその後にuseStateとuseReducerを使った利用方法を別々に説明します。本文書を読み終えるとuseContextがどのようなものかを理解することができます。

useContextとは

通常親コンポーネントから子コンポーネントにデータを渡す際は、propsを介して行います。しかし親から子、そのまた子といったように複数のコンポーネントを介してデータを渡すのはpropsでは煩雑になってきます。

ReactのContext APIを利用することでpropsを利用することなく下の階層のコンポーネントとデータの共有を行うことができます。propsに比べて覚えなければいけないことがいくつかあるので慣れるまではすべて暗記しようとせず実際に利用しないといけない時に本文書を読み直して復習してください。

最も簡単な使用例

App.jsファイルを一番親のコンポーネントとして、App.jsファイルに子コンポーネントAをimportし、コンポーネントAにコンポーネントB、コンポーネントBにコンポーネントCをimportした4階層のコンポーネントを作成します。

4階層のコンポーネント
4階層のコンポーネント

一番上の親コンポーネントであるApp.jsのファイルの中身は下記のようになります。ComponentA、B、Cについてはcomponentsディレクトリの下に作成していきます。


import React from 'react';
import ComponentA from './components/ComponentA'

function App() {
  return (
    <div style={{ textAlign: 'center' }}>
      <h1>Learn useContext</h1>
      <ComponentA/>
    </div>
  );
}

export default App;

ComponentA


import React from 'react'
import ComponentB from './ComponentB'

const ComponentA = () => {
    return (
        <div>
            <p>Componet A</p>
            <ComponentB />
        </div>
    )
}

export default ComponentA

ComponentB


import React from 'react'
import ComponentC from './ComponentC'

const ComponentB = () => {
    return (
        <div>
            <p>Componet B</p>
            <ComponentC />
        </div>
    )
}

export default ComponentB

ComponentC


import React from 'react'

const ComponentC = () => {
    return (
        <div>
            <p>Componet C</p>
        </div>
    )
}

export default ComponentC

ブラウザで見ると下記のように表示されます。

4階層のコンポーネントを表示
4階層のコンポーネントを表示

App.jsから100という数字をuseContextを利用してComponetCに渡します。

親コンポーネントでの設定

まずApp.jsでContextの作成を行います。Contextの作成はcreateContextで行います。


export const UserCount = React.createContext()

作成したUserCountをexportしているのはContextを利用するComponentCでimportを行うためです。

次はUserCount.Providerコンポーネントで数字を渡したいコンポーネントが入っているComponentAを囲みます。UserCount.Providerのタグの中のvalueに100を設定します。UserCount.Providerというタグでわざわざ囲まないといけないという処理が必要ですがこれだけで親コンポーネントでの設定は完了です。ここまでの設定ではブラウザ上には何の変化もありません。


import React from 'react';
import ComponentA from './components/ComponentA.js'

export const UserCount = React.createContext()

function App() {
  return (
    <div style={{ textAlign: 'center' }}>
      <h1>Learn useContext</h1>
      <UserCount.Provider value={100}>
      <ComponentA/>
      </UserCount.Provider>
    </div>
  );
}

export default App;

値を受け取るコンポーネントでの設定

今回はComponentCで値を受け取るのでComponenCのみ設定を行います。ComponentBなど他のコンポーネントでも値を受け取りたい場合は設定方法は同じです。

ComponentCでuseContextを利用するためimportします。また、App.jsでexportしたUserCountはここでimportする必要があります。


import React,{useContext} from 'react'
import { UserCount } from '../App'

useContextとUserCountを使ってvalueで設定した値を取り出し変数countに入れます。


const count = useContext(UserCount)

これでcountをコンポーネントCで利用することができます。


import React,{useContext} from 'react'
import { UserCount } from '../App'

const ComponentC = () => {
    const count = useContext(UserCount)
    return (
        <div>
            <p>Componet C</p>
            <p>{count}</p>
        </div>
    )
}

export default ComponentC

ブウウザで確認し、Componet Cの文字列の下に100が表示されれば、App.jsで設定した値がComponet Cに渡されたことになります。

コンポーネントCの100が表示
コンポーネントCの100が表示

これがContext, useContextを使った最もシンプルな例です。App.jsで設定した値をpropsのようにComponentA, Bを介することなくComponentCで受け取ることができました。

useStateと一緒に利用する

先程は100という値だけuseContextを利用してComponentCに渡しましたが、今回はuseStateと一緒に利用します。

App.jsでuseStateをimportとして、useStateで設定したcountとsetCountをvalueに設定します。countの初期値は100に設定しています。


import React, { useState } from 'react';
import './App.css';
import ComponentA from './components/ComponentA.js';

export const UserCount = React.createContext();

function App() {
  const [count, setCount] = useState(100);
  const value = {
    count,
    setCount,
  };
  return (
    <div className="App">
      <h1>Learn useContext</h1>
      <UserCount.Provider value={value}>
        <ComponentA />
      </UserCount.Provider>
    </div>
  );
}

export default App;

ComponentCで先程と同様の方法で受け取ります。


import React,{useContext} from 'react'
import { UserCount } from '../App'

const ComponentC = () => {

    const { count, setCount } = useContext(UserCount);

    return (
        <div>
            <p>Componet C</p>
            <p>{count}</p>
            <button onClick={() => setCount(count + 1)}>+</button>
        </div>
    )
}

export default ComponentC

受け取ったsetCountを利用してonClickイベントを利用してcountの値を増やします。

useStateで渡したcountをsetCountで増やす
useStateで渡したcountをsetCountで増やす

ボタンをクリックするとcount数字が1ずつ増えていくことが確認できます。ただの値だけではなくuseStateの値と関数もuseContextを利用してコンポーネント間で共有できることがわかりました。簡単ですね。

useReducerと一緒に利用する

useReducerはuseStateと同様に状態の管理を行うことができるのでuseContextと一緒に利用することも可能です。useStateでもuseReducerでも渡したい値をProviderのvalueに設定するので違いはなく簡単です。

useReducerを使いなれていない人によってはuseStateほど設定方法がシンプルではないので先にuseReducerの設定方法を理解しておく必要があります。

useStateの時はcountとsetCountをvalueで渡していましが、useReducerの場合はstateとdispatchを渡します。countとstateは現在の状態を保持し、setCountとdispatchはどちらも値の更新を行う関数です。actionにINCREMENTを設定しているのでactionがINCREMENTの場合はcountの値を1増やし、それ以外のACTIONの場合はcountの値を1減らします。


import React, { useReducer } from 'react';
import ComponentA from './components/ComponentA';

export const UserCount = React.createContext();

const initialState = {
  count: 100,
};

function App() {
  const reducer = (state, action) => {
    if (action === 'INCREMENT') {
      return { count: state.count + 1 };
    } else {
      return { count: state.count - 1 };
    }
  };

  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div style={{ textAlign: 'center' }}>
      <UserCount.Provider value={{ state, dispatch }}>
        <h1>Learn useContext</h1>
        <ComponentA />
      </UserCount.Provider>
    </div>
  );
}

export default App;

ComponentCではcountとsetCountの代わりにstateとdispatchを利用します。dispatchを利用するためにはACTIONを引数に設定する必要があります。先程設定したINCREMENTを設定しています。


import React, { useContext } from 'react';
import { UserCount } from '../App';

const ComponentC = () => {
  const { state, dispatch } = useContext(UserCount);
  return (
    <div>
      <p>Componet C</p>
      <p>{state.count}</p>
      <button onClick={() => dispatch('INCREMENT')}>+</button>
    </div>
  );
};

export default ComponentC;

ブラウザ上での操作はuseStateを利用した時と変わりません。

Context用のコンポーネントを作成

先ほどはApp.jsファイルの中でcreateContextを実行していましたがより汎用的にするためにContext用のコンポーネントの作成を行います。コードも最初は正直わかりにくいとは思いますが先ほど記述したコードと比較しながら確認していけばどのような処理が行われているか理解できるかと思います。useStateを利用していますがuseReducerでも設定方法は同じです。

srcフォルダの下にcontextフォルダを作成します。フォルダが作成できたらCountContext.jsファイルを作成します。

CountContext.jsファイルの中にはコンポーネント間で共有したいデータ、関数を記述します。


import { createContext, useState, useContext } from 'react';

const CountContext = createContext();

export function useCountContext() {
  return useContext(CountContext);
}

export function CountProvider({ children }) {
  const [count, setCount] = useState(100);

  const value = {
    count,
    setCount,
  };

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

他のコンポーネントでuseCountContextとCountProviderをimportできるようにexport functionで作成しています。

ContentContext.jsファイルの中でContextに関する処理をほとんど記述しているので他のコンポーネントでの記述量がほとんどありません。

App.jsファイルではCountContextコンポーネントからCountProvider関数をimportします。


import React from 'react';
import './App.css';
import ComponentA from './components/ComponentA.js';
import { CountProvider } from './context/CountContext';

function App() {
  return (
    <div className="App">
      <h1>Learn useContext</h1>
      <CountProvider>
        <ComponentA />
      </CountProvider>
    </div>
  );
}

export default App;

ComponentCコンポーネントではCountContextコンポーネントからuseCountContext関数をimportします。useContextはuseCountContextの中で使われているでComponentCでimportする必要はありません。


import React from 'react';
import { useCountContext } from '../context/CountContext';

const ComponentC = () => {
  const { count, setCount } = useCountContext();

  return (
    <div>
      <p>Componet C</p>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
};

export default ComponentC;

ブラウザ上での表示に変化はありません。

useStateで渡したcountをsetCountで増やす
useStateで渡したcountをsetCountで増やす

他のコンポーネントでも表示

ComponentA, ComponentBでもデータが共有できているのか確認するためにComponetAではcountを表示できるように変更します。setCountは必要ないので必要なcountのみ利用します。


import React from 'react';
import ComponentB from './ComponentB';
import { useCountContext } from '../context/CountContext';

const ComponentA = () => {
  const { count } = useCountContext();
  return (
    <div>
      <p>Componet A</p>
      <ComponentB />
      <p>{count}</p>
    </div>
  );
};

export default ComponentA;

ComponentBではComponentCと同様にボタンを追加し、ボタンをクリックするとcountが増える設定を追加します。


import React from 'react';
import ComponentC from './ComponentC';
import { useCountContext } from '../context/CountContext';

const ComponentB = () => {
  const { count, setCount } = useCountContext();
  return (
    <div>
      <p>Componet B</p>
      <button onClick={() => setCount(count + 1)}>+</button>
      <ComponentC />
    </div>
  );
};

export default ComponentB;

ComponentCでは先ほどはcountを表示させていましたがCompnentAで表示させるため削除しています。ComponentAで引き続きcountを表示させても問題はありません。


import React from 'react';
import { useCountContext } from '../context/CountContext';

const ComponentC = () => {
  const { count, setCount } = useCountContext();

  return (
    <div>
      <p>Componet C</p>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
};

export default ComponentC;

どちらのボタンを押してもcount数はアップします。

複数のコンポーネントで共有
複数のコンポーネントで共有

関数を追加

count数を減らせるconuntDown関数をCountContext.jsファイルに追加して共有を行い、ComponentCで実行できるように変更を行います。

追加したcountDown関数は他のコンポーネントで利用するためにはvalueオブジェクトの中に追加する必要があります。


import { createContext, useState, useContext } from 'react';

const CountContext = createContext();

export function useCountContext() {
  return useContext(CountContext);
}

export function CountProvider({ children }) {
  const [count, setCount] = useState(100);

  const countDown = () => {
    setCount(count - 1);
  };

  const value = {
    count,
    setCount,
    countDown,
  };

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

これでcountDown関数が他のコンポーネントでも利用可能になったのでComponentCを以下のように更新します。


import React from 'react';
import { useCountContext } from '../context/CountContext';

const ComponentC = () => {
  const { countDown } = useCountContext();

  return (
    <div>
      <p>Componet C</p>
      <button onClick={countDown}>-</button>
    </div>
  );
};

export default ComponentC;

ブラウザで確認すると”+”ボタンをクリックするとCountが増え、”-”ボタンをクリックするとCountが減ります。

countの数を増加、減少
countの数を増加、減少

Context用のコンポーネントを作成した場合は処理はすべてcountcontext.jsに追加するだけだけで他のコンポーネントの影響はありません。他のコンポーネントで追加した処理を利用した場合はuseCountContextから取り出す際に追加した関数を指定するだけです。

useContextの設定方法については前半部分は簡単だと感じてもらえたかもしれませんが後半部分は少し難しく感じられたかもしれませんが思っていたほど難しくないことを理解してもらえたのではないでしょうか。