
「なぜか動かない」「思った挙動と違う」
フロントエンド開発において、こうした“バグ”や“失敗例”は日常茶飯事です。
今回は、初心者がよくハマるけれど意外と体系的にまとめられていない代表的なバグ・誤解について、
setTimeoutがループして止まらない- イベントが二重に発火する
- CORSエラーの種類と読み解き方
- fetch がPOSTでも body を送らない
という4つのテーマで、なぜ起きるのか・どう直すのか・どう防ぐのか を徹底的に解説します。
Contents
✅ 1. setTimeout がループして止まらない理由
🔥 よくあるコード:
function repeatLog() {
setTimeout(() => {
console.log('Hello');
repeatLog(); // 自分自身を再呼び出し
}, 1000);
}
repeatLog();
このコード、一見タイマーで1秒ごとに実行される「正しい」使い方に見えますが、ループが止まらなくなります。
⚠️ 問題点
setTimeout の中で 再帰的に自分自身を呼び出している ため、止める手段がありません。setTimeout は setInterval と違って返り値の timeoutId を元に clearTimeout する必要がありますが、これではキャンセルできない構造です。
✅ 正しい書き方(停止できる構造に):
let timerId;
function repeatLog() {
timerId = setTimeout(() => {
console.log('Hello');
repeatLog();
}, 1000);
}
function stop() {
clearTimeout(timerId);
}
repeatLog();
// stop() を呼べば止まる
💡 ワンポイント
setTimeout再帰は「細かい間隔で制御したい」用途には便利(setIntervalより柔軟)- ただし 再帰構造では常に停止手段を設けること
- React では
useEffect内でsetTimeoutを使う場合、クリーンアップでclearTimeoutするのが基本
✅ 2. イベントが二重に発火する典型的パターン
😱 よくある現象:
- ボタンをクリックしたら2回実行された
- フォームの
submitが2回発火してバグった
🧨 原因パターン1:イベントの重複登録
document.getElementById('btn').addEventListener('click', handleClick);
function handleClick() {
console.log('Clicked!');
}
これ自体は問題ありませんが、コンポーネントの再描画や、再レンダリング時に何度も addEventListener してしまうと、重複登録 になります。
🧹 対策
useEffectのクリーンアップでremoveEventListenerを忘れずに:
useEffect(() => {
const btn = document.getElementById('btn');
btn?.addEventListener('click', handleClick);
return () => {
btn?.removeEventListener('click', handleClick);
};
}, []);
🧨 原因パターン2:フォーム submit に click イベントが重なっている
<form onSubmit={handleSubmit}>
<button onClick={handleClick}>送信</button>
</form>
この場合、onClick → onSubmit の両方が発火します。
かつ handleSubmit で e.preventDefault() をしていないと、ページがリロードされてバグになります。
✅ 対策:
- どちらか1つに絞る or
preventDefault()を忘れずに。 - 特に React Hook Form を使う場合は、onSubmitだけで完結させること。
✅ 3. CORS エラーの種類と「読める」ようになる解説
CORS(クロスオリジンリソース共有)エラーは、内容が英語で長文なので一見わかりづらく、初心者が躓きやすいポイントです。
🎯 典型的なエラー文①:
Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
📌 これは何が起きている?
- ブラウザが「他のドメインにリクエストを送ったけど、許可されてないよ」と拒否
- サーバー側の
Access-Control-Allow-Originヘッダーがない
✅ 対応策:
- サーバー側に以下を設定:
Access-Control-Allow-Origin: *
もしくは特定のオリジン:
Access-Control-Allow-Origin: http://localhost:3000
フロント側では絶対に解決できません。CORSはサーバーの設定が必要です。
🎯 典型的なエラー文②:
Response to preflight request doesn't pass access control check:
It does not have HTTP ok status.
これは、OPTIONS リクエスト(プリフライト)に対してサーバーが拒否しているというエラーです。
✅ よくあるトリガー:
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ msg: 'hello' })
});
このようにカスタムヘッダーがあると、プリフライト(事前確認)として OPTIONS リクエストが送られます。
サーバーがそれに対応していないとエラーになります。
🛠 サーバー側(Express例):
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET,POST,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
✅ 4. fetch が POST でも body を送らない原因
意外と多いのが、「POSTリクエストを送ってるのに、サーバーで req.body が空」問題。
🔍 原因パターン1:Content-Type が指定されていない
fetch('/api/send', {
method: 'POST',
body: JSON.stringify({ msg: 'hello' })
});
このように書くと、ブラウザが Content-Type を設定してくれないことがあり、サーバーがJSONと認識せずパースできません。
✅ 対策:
fetch('/api/send', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ msg: 'hello' })
});
🔍 原因パターン2:Next.js API Routes 側で req.body を使う前提であるが、ミドルウェアが未設定
Next.js(App Router)では、デフォルトで Request オブジェクトが ReadableStream のままなので、bodyの取得には await req.json() が必要です。
export async function POST(req: Request) {
const body = await req.json();
console.log(body); // ここで初めて取得できる
}
🧠 まとめ:フロントエンドの「バグ原因」を自分で読めるようになる
| 症状 | 原因 | 解決策 |
|---|---|---|
| setTimeoutが止まらない | 再帰+clearTimeoutなし | タイマーID管理&停止関数用意 |
| イベントが2回発火 | イベント重複登録・submitとclick重複 | useEffectのクリーンアップ or 絞り込み |
| CORSエラー | サーバーがCORSヘッダー未設定 | サーバー側にヘッダー追加&OPTIONS対応 |
| fetchでbodyが送れない | Content-Type未設定 or await req.json()忘れ | fetch側とAPI側両方チェック |
✍️ おわりに
今回紹介した4つのバグは、ReactやNext.jsでも日常的に起きる問題です。
初心者が「動かない…」と悩んだ時に、「なぜそうなるのか」を理解できれば、トラブルシューティング能力が一気に伸びます。
ぜひこの「バグ読解力」を武器に、よりスムーズなフロントエンド開発を目指していきましょう!









