コンテクスト(context)は、コンポーネントツリーに情報を渡す仕組みです。直接の子でなく、深い階層にある子コンポーネントであっても、useContextフックによりコンテクストの値を読み取り、その更新が受け取れます(subscribe)。
本稿はReact公式サイト「useContext」にもとづき、useContextはどう使うのか、およびどのような場合に使うとよいのかを解説します。説明内容と順序は、公式ドキュメントにしたがいました。ただし、解説はわかりやすく改め、またコード例とサンプル(StackBlitz)はTypeScriptを加えたうえで修正した部分が少なくありません。
構文
const value = useContext(SomeContext)
フックuseContextは、コンポーネントのトップレベルで呼び出してください。フックが読み込んだコンテクストから、更新された値を受け取ります。
import { useContext } from 'react';
import { ThemeContext } from './App';
export const MyComponent: FC = () => {
	const theme = useContext(ThemeContext);
}
useContextの構文はつぎのとおりです。
useContext(SomeContext) 
引数
- 
SomeContext: あらかじめcreateContextでつくったコンテクスト。- 異なるモジュールの子コンポーネントから用いるには、コンテクストを
exportしてください。 - コンテクストそのものは情報をもちません。コンポーネントから提供(provide)し、読み取れる「情報の種別」です。
 
 - 異なるモジュールの子コンポーネントから用いるには、コンテクストを
 
戻り値
useContextフックを呼び出したコンポーネントに応じたコンテクストの値が返されます。
- コンポーネントツリーを上層に向けて探し、もっとも近い
SomeContext.Providerに渡されたvalueの値です。 - ツリーの上層にプロバイダが見つからない場合は、コンテクストをつくった
createContextの引数に渡したデフォルト値(defaultValue)となります。 
戻り値は、つねにコンテクストの最新値です。Reactは、コンテクストが更新されると、読み取っているコンポーネントを自動的に再レンダーします。
注意
- 
useContextの呼び出しは、そのコンポーネント自身の戻り値(JSX)に加えられたプロバイダの値を対象としません。useContextを呼び出したコンポーネントから、ツリーの上層に向かって<Context.Provider>が探されるからです。 - コンテクストプロバイダの受け取る
valueが変わると、コンポーネントツリー内でそのコンテクストを用いるすべての子コンポーネントはReactにより自動的に再レンダーされます。- 
valueが変わったかどうかの比較はObject.isです。 - 子コンポーネントを
memoで包んでも、コンテクストは引数に受け取るpropsではないので、再レンダーは省かれません(「React + TypeScript: memoで包んだコンポーネントでもコンテクストの更新により再レンダーされる」参照)。 
 - 
 - ビルドシステムの生成する出力の中でモジュールが重複する場合(シンボリックリンクで起こり得ます)、コンテクストは壊れるかもしれないことにご注意ください。
- コンテクストによる値の受け渡しが動作するには、提供するために用いる
SomeContextと読み込む側のSomeContextは、===による比較で厳密に同じオブジェクトでなければなりません。 
 - コンテクストによる値の受け渡しが動作するには、提供するために用いる
 
使い方
コンポーネントツリーの深い下層にある子にデータを渡す
useContextフックは、コンポーネントのトップレベルで呼び出してください。読み込んだコンテクストの最新の値が受け取れます(subscribe)。
import { useContext } from 'react';
import { ThemeContext } from './App';
export const Button: FC<Props> = ({ children }) => {
	const theme = useContext(ThemeContext);
	const className = 'button-' + theme;
	return <button className={className}>{children}</button>;
};
useContextが返すのは、引数で渡したコンテクストに応じた最新の値です。Reactがコンポーネントツリーを上層に向けて探し、もっとも近いコンテクストプロバイダの値を得ます。したがって、コンテクストプロバイダは、値の使われる(useContext呼び出しする)子コンポーネントが含まれるツリー上層のいずれかの親をラップしなければならないのです。
import { createContext } from 'react';
import { Form } from './Form';
const defaultTheme = 'dark';
export const ThemeContext = createContext(defaultTheme);
function App() {
	return (
		<ThemeContext.Provider value={defaultTheme}>
			<Form />
		</ThemeContext.Provider>
	);
}
上記コード例では、コンポーネントButtonはApp直下の子ではありません。けれど、プロパティ(props)とは異なり、コンテクストは子のツリー上であればコンポーネントが間に何階層加わっても構わないのです。
export const Form: FC = () => {
	return (
		<Panel title="Welcome">
			<Button>Sign up</Button>
			<Button>Log in</Button>
		</Panel>
	);
};
import { useContext } from 'react';
import { ThemeContext } from './App';
export const Panel: FC<Props> = ({ title, children }) => {
	const theme = useContext(ThemeContext);
	const className = 'panel-' + theme;
	return (
		<section className={className}>
			<h1>{title}</h1>
			{children}
		</section>
	);
};
つぎのサンプル001では、コンポーネントはApp > Form > Panel > Buttonというツリーになっています。それでも、AppのモジュールからcreateContextでつくったコンテクスト(ThemeContext)の値は、Buttonコンポーネント内でuseContextを呼び出して読み取れるのです。themeの値はdefaultThemeですので、'dark'に対応したCSS(className)が適用されました(なお、Panelもthemeの値に応じて要素のスタイルが変わります)。
サンプル001■React + TypeScript: useContext 01
もっとも、サンプル001では、themeの値はdefaultTheme('dark')の決め打ちです。このままでは、切り替えはできせん。
[注記] useContextは、つねに呼び出されたコンポーネントツリーの上層に向けてプロバイダを探します。したがって、コンポーネント自身の戻り値(JSX)に加えられたプロバイダは対象となりません。フックは必ずコンポーネントツリーの子から親に対して呼び出してください。
コンテクストが渡すデータを更新する
コンテクストから渡すデータが決め打ちでは使えないでしょう。更新するには、値を親コンポーネントの状態変数にしてください。コンテクストのプロバイダにvalue値として渡すのはその状態変数です。
親コンポーネントAppに、チェックボックス(<input type="checkbox")を加えました。クリック(onChange)で切り替えるのが状態変数themeの値です。ツリー内の子コンポーネントは、すでにコンテキスト(ThemeContext)のvalue値が反映されるようになっています。
// import { createContext } from 'react';
import { createContext, useState } from 'react';
function App() {
	const [theme, setTheme] = useState(defaultTheme);
	return (
		// <ThemeContext.Provider value={defaultTheme}>
		<ThemeContext.Provider value={theme}>
			<label>
				<input
					type="checkbox"
					checked={theme === defaultTheme}
					onChange={({ target: { checked } }) => {
						setTheme(checked ? defaultTheme : 'light');
					}}
				/>
				Use dark mode
			</label>
		</ThemeContext.Provider>
	);
}
つぎのサンプル002で、チェックボックスによりコンポーネント(ButtonとPanel)のカラーが切り替わることをお確かめください。プロバイダに渡すvalue値が変わると、コンテクストを使っているすべての子コンポーネントは新たな値で再レンダーされるのです。
サンプル002■React + TypeScript: useContext 02
React公式サイトの「「Updating data passed via context」」には、簡単な説明とともにあと4つのサンプルが公開されています。これらは「React + TypeScript: コンテクストが渡すデータを更新するコード例」に記事を改めて解説しましたので、ご参照ください。
フォールバックされるコンテクストのデフォルト値を定める
useContext()が返すコンテクストの値は、プロバイダに渡されたvalueで決まります。したがって、createContextの引数をnullにしても直ちには問題となりません。
const ThemeContext = createContext(null);
function App() {
	const [theme, setTheme] = useState('dark');
	return (
		<ThemeContext.Provider value={theme}>
		</ThemeContext.Provider>
	);
}
ただし、ツリーを親コンポーネントに遡ってプロバイダが見つからない場合は、useContext()の戻り値はcreateContextの引数に渡したデフォルト値です。
つぎのButtonコンポーネントのように誤ってプロバイダ(<ThemeContext.Provider>)のラップから外してしまうと、useContextの戻り値(theme)はデフォルト値nullとなってしまうでしょう。
function App() {
	return (
		<>
			<ThemeContext.Provider value={theme}>
			</ThemeContext.Provider>
			<Button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
				Toggle theme
			</Button>
		</>
	);
}
export const Button: FC<Props> = ({ children, onClick }) => {
	const theme = useContext(ThemeContext);
	const className = 'button-' + theme;
	return (
		<button className={className} onClick={onClick}>
			{children}
		</button>
	);
};
createContextにデフォルト値を与えることで、問題が広がるのを抑えられます。コンテクストのデフォルト値はあとから変えられません。コンテクストの値は、前述のとおり状態変数と組み合わせて更新してください(サンプル003)。
const defaultTheme = 'dark';
export const ThemeContext = createContext(defaultTheme);
function App() {
	const [theme, setTheme] = useState(defaultTheme);
	return (
		<ThemeContext.Provider value={theme}>
			<Button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
				Toggle theme
			</Button>
		</ThemeContext.Provider>
	);
}
サンプル003■React + TypeScript: useContext 03
なお、TypeScriptを使っていれば、createContextのデフォルト値にnullを渡すと、プロバイダから与えるvalueの値に問題があると警告されるでしょう。初期値をあえてnullに定めたいときは、createContextに型づけしてください。
export const ThemeContext = createContext<string | null>(null);
コンポーネントツリーの一部のコンテクストを上書きする
コンポーネントツリーの一部を上層とはvalue値が異なるコンテクストで包めば、下層の値は上書きできます。プロバイダの入れ子による値の上書きは、いくつ行っても構いません。
コンテクストプロバイダで包まれた子コンポーネントの一部を別のプロバイダでラップする
つぎのアプリケーションは、コンテクストプロバイダに与えた値(<ThemeContext.Provider value={defaultTheme}>)に応じて、要素のカラー(CSS)を切り替えます(値は'dark'です)。
const defaultTheme = 'dark';
export const ThemeContext = createContext(defaultTheme);
function App() {
	return (
		<ThemeContext.Provider value={defaultTheme}>
			<Form />
		</ThemeContext.Provider>
	);
}
親コンポーネントでコンテクストプロバイダに包まれたFormは、改めて戻り値のJSXの一部をプロバイダ(<ThemeContext.Provider value="light">)でラップしました。加えたのは、コンポーネントFooterで、与えたvalueは異なる値("light")です。
import { ThemeContext } from './App';
export const Form: FC = () => {
	return (
		<Panel title="Welcome">
			<Button>Sign up</Button>
			<Button>Log in</Button>
			<ThemeContext.Provider value="light">
				<Footer />
			</ThemeContext.Provider>
		</Panel>
	);
};
すると、コンポーネントFooterの中の[Settings]ボタン(Button)は、ツリー上層のもっとも近いプロバイダからvalue値("light")を得て、他のふたつのボタンとは違うカラー(CSS)が適用されます。サンプル004でお確かめください。ただし、ボタンにインタラクションは与えられていません。
export const Footer: FC = () => {
	return (
		<footer>
			<Button>Settings</Button>
		</footer>
	);
};
サンプル004■React + TypeScript: useContext 04
入れ子にしたコンテクストプロバイダで親の値から子に渡す値を定める
コンテクストプロバイダを入れ子にした場合、親から受け取った値に応じて子に渡す値が定められます。
つぎのモジュールsrc/App.tsxで入れ子になっているのは、コンポーネントSectionです。
const Headings = (count: number, title: string) =>
	Array.from(new Array(count), (_) => <Heading>{title}</Heading>);
function App() {
	return (
		<Section>
			<Heading>Title</Heading>
			<Section>
				{Headings(3, 'Heading')}
				<Section>
					{Headings(3, 'Sub-heading')}
					<Section>{Headings(3, 'Sub-sub-heading')}</Section>
				</Section>
			</Section>
		</Section>
	);
}
そして、Sectionコンポーネントが返すJSXは子(children)をコンテクストプロバイダ(<LevelContext.Provider>)で包んでいます。つまり、コンポーネントとともに、コンテクストプロバイダも入れ子になるということです。valueとして渡す値は、親コンテクストの値(level)に1加算しています。
export const Section: FC<Props> = ({ children }) => {
	const level = useContext(LevelContext);
	return (
		<section className="section">
			<LevelContext.Provider value={level + 1}>
				{children}
			</LevelContext.Provider>
		</section>
	);
};
コンテクストの初期値(0)に1ずつ加算して、入れ子のコンテクストプロバイダにvalueとして渡るということです。
import { createContext } from 'react';
export const LevelContext = createContext(0);
コンポーネントSectionに差し込まれたHeadingは、useContextの値(level)に応じて、<h1>〜<h6>にテキスト(children)を加えて返します。
export const Heading: FC<Props> = ({ children }) => {
	const level = useContext(LevelContext);
	switch (level) {
		case 0:
			throw Error('Heading must be inside a Section!');
		case 1:
			return <h1>{children}</h1>;
		case 2:
			return <h2>{children}</h2>;
		case 3:
			return <h3>{children}</h3>;
		case 4:
			return <h4>{children}</h4>;
		case 5:
			return <h5>{children}</h5>;
		case 6:
			return <h6>{children}</h6>;
		default:
			throw Error('Unknown level: ' + level);
	}
};
<section>で入れ子にされた見出し(<Heading>)は、<h1>から<h6>まで順に要素を切り替えて表示されるでしょう(サンプル005)。このコード例について段階を踏んだ詳しい解説は、「React + TypeScript: コンテクストでデータを深い階層に渡す」をお読みください。
サンプル005■React + TypeScript: useContext 05
オブジェクトや関数を渡すとき再レンダーを最適化する
コンテクストを用いて渡せるのは、オブジェクトや関数も含めた任意の値です。つぎのアプリケーションは、コンテクストプロバイダにふたつのプロパティが収められたオブジェクトを与えています。プロパティのひとつ(login)は関数です。
const AuthContext = createContext(defaultContext);
function App() {
	const [currentUser, setCurrentUser] = useState('');
	const login = (response: Response) => {
		storeCredentials(response.credentials);
		setCurrentUser(response.user);
	};
	return (
		<AuthContext.Provider value={{ currentUser, login }}>
			<Page />
		</AuthContext.Provider>
	);
}
コンポーネント内に定められた関数は、再レンダーのたびに定義し直されることに気をつけなければなりません。再定義された関数は、中身が変わらなくとも、参照にもとづいて別と評価されます。すると、コンテクストの中の関数は定義が変わったとみなされ、useContext(AuthContext)を用いる子コンポーネントはすべて再レンダーされるのです。
アプリケーションがまだ小さければ、気にしなくても構いません。とはいえ、コンテクストの値としたオブジェクトのもうひとつのプロパティ(currentUser)も変わっていないなら、無駄に再レンダーしなくてもよいでしょう。
関数loginをuseCallbackで包めば、Reactは関数定義が実質的に変わったか確かめられます。さらに、コンテクストプロバイダのvalueに渡すオブジェクト(contextValue)をメモ化してしまえるのがuseMemoです。このようにして、パフォーマンスが最適化できます。
function App() {
	// const login = (response: Response) => {
	const login = useCallback((response: Response) => {
		storeCredentials(response.credentials);
		setCurrentUser(response.user);
		// };
	}, []);
	const contextValue = useMemo(
		() => ({
			currentUser,
			login,
		}),
		[currentUser, login]
	);
	return (
		// <AuthContext.Provider value={{ currentUser, login }}>
		<AuthContext.Provider value={contextValue}>
		</AuthContext.Provider>
	);
}
loginに関数を返すuseCallbackに依存はありません([])。contextValueにオブジェクトを収めるuseMemoの依存値は、currentUserとloginです。したがって、コンポーネントAppは再レンダーされても、useContext(AuthContext)を呼び出している子コンポーネントはcurrentUserの値が変わらないかぎり再レンダーされません。
詳しくは、「React + TypeScript: useCallbackフックの使い方と使いどころ」と「React + TypeScript: useMemoフックの使い方と使いどころ」をお読みください。
トラブルへの対応
コンテクストプロバイダに与えられているvalue値がコンポーネントから受け取れない
この問題でよくある理由はつぎの3つです。
- 
useContextを呼び出すコンポーネントは、コンテクストプロバイダ(<SomeContext.Provider>)に包まれた子でなければなりません。- 自分のコンポーネントが返すJSXにレンダーしたコンテクストプロバイダの
value値は得られません。 - コンテクストプロバイダ(
<SomeContext.Provider>)は、コンポーネントの外側、ツリーの上層に移してください。 
 - 自分のコンポーネントが返すJSXにレンダーしたコンテクストプロバイダの
 - コンポーネントがコンテクストプロバイダ(
<SomeContext.Provider>)のラップから外れていることが考えられます。- React Developer Toolsを使ってツリー階層が正しいか確かめましょう。
 
 - コンテクスト(
SomeContext)の参照が、プロバイダコンポーネントと利用側コンポーネントとの間で食い違っているかもしれません。- シンボリックリンクを使っていたり、ビルドツールの問題により、ふたつのブジェクトは異なって認識される場合があります。
 - ふたつの参照を
window.SomeContext1やwindow.SomeContext2といったグローバル変数に割り当て、コンソールでwindow.SomeContext1 === window.SomeContext2が成り立つか確かめてください。- 等しくない場合は、ビルドツールのレベルで解決しなければなりません。
 
 
 
コンテクストのデフォルト値を変えても得られる値がつねにundefinedになる
createContextの引数に与えたデフォルト値は、コンポーネントツリーの上層にコンテクストプロバイダがない場合に返されます。したがって、つぎのような点をお確かめください。
- コンポーネントツリーの上層にコンテクストプロバイダはあって、
valueが与えられていないのかもしれません。- 
valueがなければ、value={undefined}と同じです。 
 - 
 
// 🚩 NG: コンテクストプロバイダにvalueが与えられていない
<ThemeContext.Provider>
	<Button />
</ThemeContext.Provider>
- 子コンポーネントに
propsを渡すときと勘違いして、value以外の名前を使ってしまうことがあります。 
// 🚩 NG: コンテクストの値を渡すのはvalueです
<ThemeContext.Provider theme={theme}>
	<Button />
</ThemeContext.Provider>
ただ、どちらの場合もReactから警告が示されるでしょう。コンテクストプロバイダに値はvalueで渡してください。
// ✅ OK: コンテクストの値はvalueで渡す
<ThemeContext.Provider value={theme}>
	<Button />
</ThemeContext.Provider>
前述のとおり、createContextの引数に与えたデフォルト値が返されるのは、コンポーネントツリーの上層にコンテクストプロバイダがない場合です。ツリー上層にコンテクストプロバイダ<SomeContext.Provider value={someValue}>はあり、someValueがundefinedなら、コンポーネントからuseContext(SomeContext)を呼び出して受け取るコンテクスト値はundefinedになるでしょう。