プログラミング

JavaScriptのCannot read properties of nullを直す方法|querySelectorの確認手順

addEventListenerやtextContentでnullのエラーが出たとき、取得結果・idとclass・実行順を順番に確認する方法を解説。deferの使い方と、練習問題の解答も掲載します。

ボタンを押す処理を書いたのに、ページを開いた瞬間に赤いエラー。「ボタンはちゃんと書いたのに!」と困りますよね。

Cannot read properties of null (reading 'addEventListener')

この場合は、操作するはずの要素を取得できているかから調べます。この記事では、querySelector()でHTML要素を探す処理に絞り、名前と実行順の両方を確認します。エラーの細かい文言はブラウザーによって変わります。

DOM操作そのものが初めてなら、ボタンで文章を変える基本から試せます。

エラー行のドットの左側を見る

次のコードで止まったとします。

const button = document.querySelector("#saveButton");

button.addEventListener("click", () => {
  console.log("押されました");
});

button.addEventListenerのドットの左側はbuttonです。エラー行の直前へ、次の確認を追加します。

console.log(button);

ブラウザーの開発者ツールにあるConsoleを開き、ページを再読み込みしてください。nullと出たら、ボタンを取得できていません。

nullは「値がない」ことを明示する値です。querySelector()は、指定した条件に一致する最初の要素を返し、見つからなければnullを返します。そこでaddEventListenerを読み取ろうとして止まっています。MDN:querySelector()

message.textContent = "完了";でCannot set properties of nullが出る場合も、調べるのはmessageです。エラーが指す行と、そこで使う値をセットで見ましょう。

HTMLの名前とセレクターを比べる

セレクターは、どの要素を探すかを指定する書き方です。次のボタンなら、対応はこうなります。

<button id="saveButton" class="action" type="button">保存する</button>
指定 探す対象
"#saveButton" idがsaveButtonの要素
".action" classにactionを持つ要素
"button" button要素

"#saveBtn"は別の名前です。".saveButton"は、idではなくclassを探しています。大文字・小文字、末尾の文字、#と.を、そのまま見比べてください。

名前が合っていれば、今開いているページにその要素があるかも見ます。別のHTMLを開いていたり、ボタンのないページでも共通のJavaScriptを読み込んでいたりすることがあります。

なお、"["のような不正なセレクターは、見つからない結果のnullではなくSyntaxErrorになります。表示されたエラー名を飛ばさないことも大事です。

要素が作られる前に探していないか確認する

HTMLの中にボタンがあっても、探した瞬間に使えるとは限りません。

ブラウザーがHTMLから作る、プログラムで操作できる文書の構造をDOMと呼びます。普通のスクリプトを次の位置で実行すると、まだ下のボタンはDOMにありません。

<script>
  const button = document.querySelector("#saveButton");
  console.log(button); // ここではnull
</script>

<button id="saveButton" type="button">保存する</button>

後からボタンが作られても、変数へ入ったnullが自動で要素に変わるわけではありません。取得する処理を、要素ができた後へ移します。

HTMLへ直接書く場合

小さなページなら、操作する要素の後ろ、</body>の直前へscriptを置くと分かりやすいです。

外部のJavaScriptファイルを使う場合

app.jsへ分けているなら、次のように読み込めます。

<script src="app.js" defer></script>

deferを付けた外部の通常スクリプトは、HTMLの解析が終わってから実行されます。ここでいう通常スクリプトは、type="module"のないものです。HTML内に直接書く<script defer>...</script>では、この待ち方になりません。asyncも同時に付けると実行の仕方が変わります。HTML標準:script要素

エラーが消えたことと、動くことを分ける

次の?.は、左側がnullやundefinedなら、その先の呼び出しを省く書き方です。

button?.addEventListener("click", () => {
  console.log("押されました");
});

これでエラー表示が消えても、ボタンの処理は登録されません。必ずあるはずのボタンなら、取得できない原因が残っています。

一方、複数ページで共通のファイルを使い、ボタンのないページもある設計なら、存在するときだけ登録する方法は適切です。MDN:オプショナルチェーン

また、通信が終わった後などにJavaScriptで追加する要素は、HTMLの解析完了を待つだけでは取得できません。要素を追加した後に取得して処理を登録します。「いつ作るか」と「いつ探すか」を並べると整理できます。

練習:名前と実行順の間違いを直す

次をbodyの中へ書きました。ボタンを押すと文章が「できました」へ変わるように直してください。

<script>
  const message = document.querySelector("#message");
  const button = document.querySelector(".showButton");

  button.addEventListener("click", () => {
    message.textContent = "できました";
  });
</script>

<p id="message">まだ押されていません</p>
<button id="showButton" type="button">変更する</button>

模範解答

1つのHTMLファイルとして試せる完成例です。

<!doctype html>
<html lang="ja">
  <head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>ボタンで文章を変更</title>
  </head>
  <body>
    <p id="message" aria-live="polite">まだ押されていません</p>
    <button id="showButton" type="button">変更する</button>

    <script>
      const message = document.querySelector("#message");
      const button = document.querySelector("#showButton");

      button.addEventListener("click", () => {
        message.textContent = "できました";
      });
    </script>
  </body>
</html>

解説

直したのは、idを探す#showButtonと、要素より後ろへ移したscriptです。片方だけ直しても、もう片方の原因が残ります。

保存してページを再読み込みし、クリック後の文章を確認してください。うまくいかなければConsoleで最初のエラーを読み、取得結果を表示するところへ戻ります。aria-liveは文章の変化を読み上げなどへ知らせる指定で、今回のエラーの原因とは別の配慮です。

続けて読む

← 記事一覧へ戻る