第76章:useContext の型チェック
〜 null をこわがらなくていいカスタムフックを作ろう 〜
1️⃣ この章でやりたいこと ✨
この章のゴールはたったひとつです👇
「
useContextのnull問題を、カスタムフックでまるごと隠す」
createContext<UserContextValue | null>(null)みたいにnullを許す形で Context を作った- そのせいで
useContext(UserContext)の型がUserContextValue | nullになってしまう - どこでも毎回
if (!userContext) { ... }とか書くのダルい…
という状況を、カスタムフックで一発解決します 😎
2️⃣ いまの前提コードを整理しよう 📚
ここでは例として「ユーザー名」を共有する Context を使います。 (71〜75章までで作ったものを、ちょっとだけ思い出す感じです)
例として src/contexts/UserContext.tsx を想定します。
今までの書き方(おさらい)
// src/contexts/UserContext.tsx
import { createContext, useState } from "react";
type UserContextValue = {
userName: string;
setUserName: (name: string) => void;
};
// 👇 「まだ値がないよ〜」という意味で null を入れておく
export const UserContext = createContext<UserContextValue | null>(null);
export function UserProvider({ children }: { children: React.ReactNode }) {
const [userName, setUserName] = useState("ゲスト");
const value: UserContextValue = {
userName,
setUserName,
};
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
ポイント
- 型は
UserContextValue | null createContextの初期値はnullUserProviderがいるときだけ、本物の値が入る
この「| null」があるせいで、
useContext(UserContext) を使う側では 毎回 null チェックが必要になります。(LogRocket Blog)
3️⃣ useContext をそのまま使うと何がつらい?😢
たとえば、ユーザー名を表示するコンポーネント。
// src/components/UserNameLabel.tsx
import { useContext } from "react";
import { UserContext } from "../contexts/UserContext";
export function UserNameLabel() {
const userContext = useContext(UserContext);
if (!userContext) {
// Provider で囲まれていなかった場合の保険
return <p>コンテキストが使えません 😭</p>;
}
return <p>こんにちは、{userContext.userName} さん 🌸</p>;
}
問題点
- 毎回
if (!userContext) { ... }を書くのがめんどくさい - うっかり書き忘れると、
userContextがnullのまま使われてバグる - JSXの中も
userContext?.userNameだらけになりがち…
これを1か所でチェックして、他では気にしなくていいようにしたい、 というのがこの章のテーマです 💪
4️⃣ カスタムフック useUserContext を作る 🎣
やりたいこと
useContext(UserContext)を ラップしたカスタムフックを作る- 中で一度だけ
nullチェックする nullのときは はっきりしたエラーを投げる- それ以外のコンポーネント側では
const { userName } = useUserContext();と、安全に使えるようにする
このパターンは TypeScript + React ではかなりよく使われる「定番」になっています。(GitHub)
5️⃣ 実装:UserContext に安全ベルトをつける 🛟
同じファイル UserContext.tsx に、カスタムフックを足します。
// src/contexts/UserContext.tsx
import { createContext, useContext, useState } from "react";
type UserContextValue = {
userName: string;
setUserName: (name: string) => void;
};
export const UserContext = createContext<UserContextValue | null>(null);
export function UserProvider({ children }: { children: React.ReactNode }) {
const [userName, setUserName] = useState("ゲスト");
const value: UserContextValue = {
userName,
setUserName,
};
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
}
// ⭐ この章の主役:安全なカスタムフック
export function useUserContext(): UserContextValue {
const context = useContext(UserContext);
if (context === null) {
// React DevTools のコンソールに出るメッセージ
throw new Error("useUserContext は UserProvider の中で使ってください。");
}
// ここに来るときは context は絶対に null じゃない!
return context;
}
ここがポイント!
-
function useUserContext(): UserContextValue { ... }- 戻り値の型に
| nullがなくなるのが大事 🎯
- 戻り値の型に
-
中で
useContext(UserContext)を呼んで…if (context === null) { throw new Error(...) }
-
nullだったら「ちゃんと Provider で囲んでね!」とエラーを出す -
nullじゃなければ、そのまま返す → 以降はUserContextValueとして安心して使える
6️⃣ 使う側のコードがこう変わる ✨
さっきの UserNameLabel を、カスタムフック版に書き換えてみます。
// src/components/UserNameLabel.tsx
import { useUserContext } from "../contexts/UserContext";
export function UserNameLabel() {
// ⭐ もう null チェック不要!
const { userName } = useUserContext();
return <p>こんにちは、{userName} さん 🌸</p>;
}
スッキリしたの、分かりますか?😊
-
if (!userContext)が 消える -
JSXも
{userName}と素直に書ける -
もし Provider で囲み忘れていたら?
- 画面はエラーになってくれて、
- コンソールに 「useUserContext は UserProvider の中で使ってください」 と教えてくれます
「気づかないままヘンな表示になる」より、 バキッとエラーが出てくれた方がデバッグしやすいんです 🔍
7️⃣ 図でイメージをつかもう 🧠
UserProvider(親)と、UserNameLabel(子)、その間に
useUserContext が入っているイメージを Mermaid で描いてみます。
UserProviderが Context の値を用意して配る- 子コンポーネントは直接
useContextではなくuseUserContextにだけ頼む useUserContextの中で nullチェックしてから 値を返す
という流れになっています 🎵
8️⃣ 「外で使ったらどうなるの?」実験 💣
ちょっとわざとミスしてみましょう。
App.tsx をこんなふうに書いたとします(わざとミスです)。
// src/App.tsx
import { UserNameLabel } from "./components/UserNameLabel";
// ❌ UserProvider で囲み忘れてる!
function App() {
return (
<div>
<h1>テストアプリ</h1>
<UserNameLabel />
</div>
);
}
export default App;
この状態で画面を開くと…
- 画面がエラーになって止まる
- コンソールに 「useUserContext は UserProvider の中で使ってください。」 というメッセージが出る(さっき throw したやつ)
👉 **「あ、Provider で囲んでなかったんだな」**とすぐ分かるので、 バグの場所を見つけやすくなります 💡
9️⃣ よくある「別解」と、この教材でのオススメ ✅ / ❌
TypeScript + Context で、null 問題をどう扱うかはいくつか流派があります。
❌ 型アサーションでゴリ押しするパターン
const userContext = useContext(UserContext)!;
// か
const userContext = useContext(UserContext) as UserContextValue;
- TypeScriptには怒られない
- でも ほんとうに null が来たら即クラッシュ 💥
- どこで間違ってるのか分かりにくい
→ この教材では オススメしません。
✅ カスタムフックでチェックしてあげるパターン(今やったやつ)
export function useUserContext(): UserContextValue {
const context = useContext(UserContext);
if (context === null) {
throw new Error("useUserContext は UserProvider の中で使ってください。");
}
return context;
}
- 型的にも安全(戻り値は
UserContextValue) - 実行時にも安全(ミスってたらメッセージ付きでエラー)
- コンポーネント側はスッキリ ✨
TypeScript の React チートシート的な資料でも 「ランタイムチェック+カスタムフック」のパターンがよく紹介されています。(GitHub)
🔟 ちょこっと応用:いろんな Context で同じパターンを使う 🌈
このカスタムフックのアイデアは、他の Context にもそのまま使えます。
例:テーマ(ライト/ダーク)を管理する ThemeContext を作ったとしたら…
useThemeContextuseAuthContextuseSettingsContext
…など、それぞれの Context ごとに 「安全なカスタムフック」をセットで作る というのが、かなり実務っぽいパターンです 💼
1️⃣1️⃣ 練習タイム 📝(軽めでOK)
時間があるときに、こんな感じで手を動かしてみてください ✋
-
ThemeContextを作ってみるtheme: "light" | "dark"toggleTheme: () => void- を持つ
ThemeContextとThemeProviderを作る useThemeContextも 同じパターンで作る
-
ThemeContextを使って- ページの背景色や文字色を切り替えるコンポーネントを作る
useThemeContextを使って、themeを読み取る
-
わざと
ThemeProviderで囲むのを忘れてみて、- どんなエラーが出るか
- そのメッセージで原因が分かりやすいか
- を確認してみる
まとめ 🌸
この章で覚えてほしいことはただひとつ:
「
useContextにそのまま触らず、カスタムフック経由にしてnullチェックを1か所に集める」
これだけで、
- コンポーネントが読みやすくなる ✨
- TypeScript の型もスッキリする 💻
- ミスしたときにエラーで教えてくれる 🐞
という、いいことづくめです。
次の章からも、Context を使うときは **「Context本体 + Provider + 安全なカスタムフック」**の3点セットを 基本形として使っていきますよ〜 🎉