初級10分で読める

【解決】Cannot read properties of undefined/null エラーの原因と修正

ReactでよくあるCannot read properties of undefined (reading 'xxx')エラーを解説。非同期データの読み込み前アクセス・undefinedなprops・配列メソッドのチェーンなど原因別に修正方法を示します。

このエラーを見たとき、「ちゃんとfetchしてるのになんでnullなんだ」という気持ちになりやすい。

TypeError: Cannot read properties of undefined (reading 'name')
TypeError: Cannot read properties of null (reading 'map')

コードを直す前に、なぜこのエラーが起きるのかを理解しておいたほうがいい。原因がわかると、次に同じミスをしなくなるからだ。

Reactは「描いてからデータを取りに行く」

初めてReactを書いたとき、こういう順番で動くと思いやすい。

useEffectでfetchする → データが届く → 画面に表示される

実際の流れはこうだ。

① コンポーネントが実行される(userはまだnull)
② Reactが画面を描く          ← このタイミングでエラーが起きる
③ useEffectが走り始める
④ fetchが始まる(ネットワーク通信が走る)
⑤ データが届く
⑥ setUser(data) → ①に戻って再描画
⑦ 今度はuserに値があるので正しく表示される

useEffectは描画の「後」に実行される。これはReactの仕様だ。なのでfetchを書いていても、最初の描画では必ずデータが空の状態で画面を作ろうとする。

このしくみを知っていれば、エラーの読み方が変わる。「nullのプロパティにアクセスした」ではなく、「データが届く前に描画が走った」という話だとわかる。

原因1:非同期データがまだ届いていない(最頻出)

上の流れを踏まえてコードを見ると、エラーの理由がわかる。

// ❌ なぜ失敗するのか
function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState(null)  // 初期値はnull

  useEffect(() => {
    fetchUser(userId).then(setUser)  // これは描画の後に走る
  }, [userId])

  return <div>{user.name}</div>  // 最初の描画でnull.nameを参照 → エラー
}

「fetchしてるのになんでnullなんだ」ではなく「fetchが終わる前に描画が走っている」という話だ。

修正は、「データがない間は別の表示をする」こと。

// ✅ データが届くまでの表示を用意する
function UserProfile({ userId }: { userId: number }) {
  const [user, setUser] = useState<User | null>(null)

  useEffect(() => {
    fetchUser(userId).then(setUser)
  }, [userId])

  if (!user) return <div>読み込み中...</div>  // データがない間はここで止まる

  return <div>{user.name}</div>  // ここに来たとき、userは必ず存在する
}

if (!user) return のパターンは「早期リターン」と呼ばれる。useEffectでデータ取得するコンポーネントではほぼ毎回登場する書き方なので、定番として覚えておいていい。

原因2:配列が undefined のまま .map() を呼んでいる

同じ「存在しないかもしれない値」の問題が、配列で起きるパターン。

// ❌ items が省略されたとき、.map() が存在しないのでエラー
function ItemList({ items }: { items?: string[] }) {
  return (
    <ul>
      {items.map((item) => <li>{item}</li>)}
    </ul>
  )
}

? がついているということは「渡されないこともある」ということだ。渡されなかったとき itemsundefined で、undefined.map() は存在しない。

修正の方法は2つある。

// ✅ パターン1:「必ず配列として受け取る」ようにデフォルト値を設定する
function ItemList({ items = [] }: { items?: string[] }) {
  return (
    <ul>
      {items.map((item, i) => <li key={i}>{item}</li>)}
    </ul>
  )
}
// ✅ パターン2:オプショナルチェーンで「undefinedなら何もしない」
function ItemList({ items }: { items?: string[] }) {
  return (
    <ul>
      {items?.map((item, i) => <li key={i}>{item}</li>)}
    </ul>
  )
}

パターン1は「コンポーネントの中では配列として扱える」保証があるので、その後の処理がシンプルになる。パターン2は undefined のまま外に持ち出せるが、undefined のときは何も表示されない。どちらが正解かは要件次第だが、配列を描画するコンポーネントではパターン1のほうが見通しが良くなることが多い。

原因3:オブジェクトの途中がundefined

APIレスポンスで order.customer.address.city のように深い構造にアクセスするとき、途中が undefined だとエラーになる。

// ❌ customer が optional なのに直接 .name を参照している
type Order = {
  id: number
  customer?: { name: string; address?: { city: string } }
}

function OrderSummary({ order }: { order: Order }) {
  return (
    <div>
      <p>{order.customer.name}</p>         {/* customerがなければエラー */}
      <p>{order.customer.address.city}</p> {/* addressがなければエラー */}
    </div>
  )
}

「customerが来ないはずがない」と思っていても、APIは予期せず nullundefined を返すことがある。

オプショナルチェーン(?.)を使うと、途中が undefined ならそこで処理が止まって undefined を返す。エラーにならない。

// ✅ 途中がundefinedでもクラッシュしない
function OrderSummary({ order }: { order: Order }) {
  return (
    <div>
      <p>{order.customer?.name ?? '未設定'}</p>
      <p>{order.customer?.address?.city ?? '未設定'}</p>
    </div>
  )
}

?.(オプショナルチェーン)と ??(nullish coalescing)はセットで使うことが多い。「undefinedかもしれないが、そのときはこの値を使う」という意図が一行で書ける。

原因4:useRefのcurrentにマウント前にアクセスしている

useRef でDOM要素を操作するとき、同じ「タイミング」の問題が起きる。

// ❌ コンポーネントの実行中はまだDOMが存在しない
function InputWithFocus() {
  const inputRef = useRef<HTMLInputElement>(null)

  inputRef.current.focus()  // null.focus() → エラー

  return <input ref={inputRef} />
}

refにDOM要素がセットされるのは、Reactが画面を描いた後だ。returnの前(コンポーネントが実行されている最中)はまだ currentnull のまま。

原因1と同じ構造だ。「描く前にアクセスしようとしている」。

// ✅ useEffectの中(描画後)で操作する
function InputWithFocus() {
  const inputRef = useRef<HTMLInputElement>(null)

  useEffect(() => {
    inputRef.current?.focus()  // 描画後に実行されるので安全
  }, [])

  return <input ref={inputRef} />
}

TypeScriptで事前に気づく

tsconfig.jsonstrict: true を有効にしておくと、TypeScriptが「ここはnullかもしれない」と書いている時点で教えてくれる。

{
  "compilerOptions": {
    "strict": true
  }
}

このエラーで画面が真っ白になって原因を探すより、エディタ上で赤線を見つけるほうがずっと早い。TypeScriptの警告が出たとき「うるさい」と感じる場面もあるが、Cannot read properties 系のエラーはほぼTypeScriptが事前に教えてくれるものなので、警告を無視しないほうがいい。

このエラーを防ぐ1つの考え方

「データは遅れて来る」という前提で書く。

useEffectで取得する非同期データも、省略可能なpropsも、useRefで参照するDOMも——「ある瞬間は存在しない」という状態がある。その状態での動作をコードに書いておくことが、このエラーへの根本的な対処だ。