【実録Q&A】初学者に伝授したデバッグ思考の体系化
投稿日: 2025年10月26日
ある受講生さんからいただいた質問が、どれも「私もそこ悩んだ!」という内容で、
少し前の自分を思い出しました。
同じように詰まる方も多いと思うので、今回やり取りした内容をもとに 「こう考えるとスッと理解できるかも」というポイントをまとめます。
コードそのものよりも、デバッグの進め方・考え方の整理として読んでもらえると嬉しいです。
まず最初に、今回のやり取りから見えてきたポイントをまとめます。
エラーを直す前に、「何に対して何をしているか」を言語化する
console.log と Network タブで「実態」を確かめる
状態(state)は使う場所の“最小共通の親”に置く
型は“1つ定義して皆で使う”が原則(再定義はバグのもと)
具体的にみていきたいと思います。
このような形でご質問いただきました。
余談ですが、私、7,2歳の育児中につき夕方からしばらくスマホで返信はできるけど丁寧には無理な時間帯になります。あとで解説送りますって内容になります。(17-21時くらいまでそんな感じ)
その後の回答
posts.find(...) で落ちる(他にもmap,filterなどの配列用メソッド)
実はレスポンスが posts: [...] ではなくpost: {...}(単体オブジェクト)だった
findしようとした変数の値がundefinedだった
配列操作(map/find/filter)を呼んでいる相手が、本当に配列か? をまず疑いましょう
おまけですが、命名も合わせるposts(複数)か post(単数)と思考がブレないです。
複数形の変数に入ってる値は基本配列だと思うのが一般的なので、単数(オブジェクト等)なのか複数形なのかは意識して命名した方が良いです。
// ❌ オブジェクトに対して配列前提でfindしようとしている
const posts = data.posts;
const post = posts.find(post => post.id === Number(postId));// ✅ 詳細APIは単体オブジェクトのことが多い
console.log("data", data); // ← まず中身を見る
setPost(data.post); // ← 命名は実態に合わせるconsole.log(data) で現物を見る (dataはpostsをもってるか?postsは配列なのか?確認する)
キーと形を言語化してみる(例:「data は message と post を持つ。post はオブジェクト」)
変数名を形に合わせてrenameするposts → post
UI 側の想定も単数ベースに直す
どんなエラーも、「実際の中身を目で見る」ことから始まります。
思ってた値が入ってないってことは多々あるので、しっかり確認しましょう!!console.logは想定と現実と照らし合わせるツールみたいなものです。
このような質問をいただきました。
入力欄 Input コンポーネントに閉じたい気持ちがある
送信ボタンやバリデーションは親で扱う(何気なくそうなってる感じだと思う)
ステートどこで定義したらいいんだ?
その値を“どこで使うか”で置き場所を決めます
複数コンポーネントで使う/親で送信する → 親に置く
// 親
const [name, setName] = useState("");
const handleSubmit = () => { /* name を使って送信 */ };<Input value={name} onChange={setName} />
<Button onClick={handleSubmit}>送信</Button>// 子 (Input)
export const Input = ({ value, onChange }:
{ value: string; onChange: (v: string) => void }) => {
return
<input value={value}
onChange={(e) =>
onChange(e.target.value)}
/>;
};React Hook Formを採用する場合も、親フォームが管理主体になる前提は同じです。
form関連のコンポーネントのトップレベル(親)で定義します。
これめちゃ疑問に思いますよね。
このような質問をいただきました。
どちらでも間違いじゃない
チームでルールを決めて揃えることが正解です。
規則性なくバラバラは良くないと思います。
ただし、 ユニオン型などは type のほうが表現しやすい場面が多いです。
// 例:ユニオン型はtypeが素直
export type Status = "draft" | "published";// 例:interfaceでも良い
export interface Post {
id: number;
title: string;
status: Status;
}2ファイル以上で使う型は共通ファイルからexportして重複定義しない
これが正解です。(断言しちゃう)
別々で定義すると、後から「型エラー地獄」に陥ってしまう可能性が大です!!
あっち変えたらこっちがエラー吐いて、こっち修正したらエラー消えたけどあっちがエラー、、、とかですね。TS初心者あるあるです。(経験者)
なぜ型を定義しているかを考えてみましょう!!
データの型を担保するためです。同じデータなのに型定義を別でしちゃうと事故率アップです!自分を守るためにも同じデータに充てる型は一箇所で定義しましょう!
順番の固定は、迷いを減らす最強のTipsです!
1. エラー文を読む(「何に対して」「何をしようとして」失敗してる?)
2. 値の中身を見る(console.log(value))
3. 値の確認(配列?オブジェクト?null/undefined?)
4. API絡みならNetwork タブで URL / ステータス / レスポンス を確認
5. 命名を整える(おまけですが、形に合わせて post / posts など)
// APIレスポンスのデバッグ最小セット
const res = await fetch(`/api/posts/${id}`);
console.log("res", res);
const data = await res.json();
console.log("data", data);「どこが原因か」ではなく、「どの段階から想定とズレているか」を探すのがデバッグです。
エラーを吐いている場所に近いところから根本原因まで遡っていくことはよくあることなので、デバックしながらエラーが出る原因の本質を探っていきましょう!
「何が入っているはずで、今は何が入っているのか」
言語化しながら比べるだけで必ず前に進みます。
AIに頼りすぎてる人は何をしようとしているか理解が追いついておらず、言語化ができないことが多いと思います。
無理と感じたら一旦状況の理解に務めるといいと思います!
想定:data.posts は配列
→実際:data は { message, post }」→ ズレの場所が確定
言語化 = デバッグみたいなものだと思います。
説明できる状態は理解できているに限りなく近いと思宇野でまずはやってみてください!
それ疑問に思ったことあるって内容ありましたでしょうか?
私はどれも疑問に思いながら進めてきた経験があるので共感できる質問ばかりでした。
TAとしてず〜っと並走はできないからこそ、自分で原因に近づくための考え方を一緒に育てたいと思っています。
「正解のコード」より先に、 “どうやって確かめ、どこから直すか” の順番を手に入れる。
その積み重ねが、いちばん早いと考えます。
同じところで悩んでいる誰かの、最初の一歩になれば嬉しいです。
また、実際のスクショ貼らせていただきましたが(了承得てます)、オープンな場で質問しにくいという方もいると思うので、こんな風に聞いてもちゃんと答えてもらえるんだという認知のきっかけになればいいなと思います。
記事公開後にタマネギ先生からコメントいただきまして、確かにすぎたので補足します!(神!ありがとうございます!)
自分はちょい整形モードのconsole.log(JSON.stringify(data, null, 2));も愛用してます。
とのことで、確かにそのままだと中身が見えないことがあります![object, object]と表示されてそうじゃない!ってなるんですよねw
そんなときは、整形してJSONとして出力する方法が便利です👇
console.log(JSON.stringify(data, null, 2));null, 2 の部分でインデントを指定しているので、階層構造が見やすくなります。
例えレスポンスが複雑なオブジェクトでも、console.log一発で全体の構造を確認できます。
私、console.log(JSON.stringify(data));これはよくやってたんですが、第三引数まで渡せるの知りませんでした!インデントまで指定できるなんてめちゃ便利ですね💡
デバック力つけてどんなエラーでもドンと来いなメンタル身につけていきましょう!