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は文章の変化を読み上げなどへ知らせる指定で、今回のエラーの原因とは別の配慮です。