Reactでフォーム送信ができない|preventDefaultの使い方
Reactでフォーム送信が動作しない問題とpreventDefaultの正しい使い方を詳しく解説。フォームハンドリングの基本からトラブルシューティングまで実践的に紹介
あなたも、Reactでフォームを作った時にこんな経験はありませんか?
「送信ボタンを押してもページが更新されちゃう...」「フォームデータが送信されない...」「何かうまくいかない...」
フォームの実装って、React開発で避けて通れない重要な部分ですよね。 でも、初心者の方にとってはちょっと複雑で、特にpreventDefaultって何?という感じですよね。
でも大丈夫です!
この記事では、Reactでフォーム送信ができない原因と、preventDefaultの正しい使い方について詳しく解説します。 基本的な概念から実践的なトラブルシューティングまで、コード例と一緒に学んでいきましょう。
フォーム送信の基本って何?まずはここから理解しよう
まず、HTMLフォームとReactでのフォーム処理の違いを理解しましょう。 この違いがわかると、なぜpreventDefaultが必要なのかがスッキリしますよ。
普通のHTMLフォームはこんな動きをします
HTMLフォームは、デフォルトでサーバーにデータを送信してページを更新します。
<!-- 通常のHTMLフォーム -->
<form action="/submit" method="POST">
<input type="text" name="username" />
<input type="email" name="email" />
<button type="submit">送信</button>
</form>
このHTMLフォームでは、こんな流れで動きます。
- 送信ボタンがクリックされる
- フォームデータが
/submit
にPOSTされる - ページが新しいURLに移動する(ページ更新)
昔ながらのWebサイトなら、これで問題ありません。 でも、Reactでは少し違うんです。
Reactでのフォーム処理は何が違うの?
Reactでは、通常はページを更新せずにJavaScriptでフォームを処理します。
// ❌ 問題のあるReactフォーム
function ContactForm() {
const [formData, setFormData] = useState({
username: '',
email: ''
});
const handleSubmit = () => {
// フォームデータの処理
console.log('送信データ:', formData);
// API呼び出しやバリデーション等
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
/>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
/>
<button type="submit">送信</button>
</form>
);
}
でも、このコードには問題があります。
送信ボタンをクリックすると、handleSubmit
が実行された後、HTMLフォームのデフォルト動作も実行されてしまうんです。
その結果、ページが更新されて、Reactの状態やコンソールログが失われてしまいます。
「あれ?何も起こらない...」となるのは、これが原因なんです。
preventDefaultって何?これがフォーム処理の鍵
preventDefaultは、ブラウザのデフォルト動作を阻止するためのメソッドです。
簡単に言うと、「ブラウザさん、いつものことはしないで!JavaScriptに任せて!」ということを伝える方法なんです。
eventオブジェクトを理解しよう
まず、イベントオブジェクトという便利なものを見てみましょう。
// イベントオブジェクトの確認
function ExampleForm() {
const handleSubmit = (event) => {
console.log('eventオブジェクト:', event);
console.log('event.type:', event.type); // "submit"
console.log('event.target:', event.target); // <form>要素
console.log('event.preventDefault:', typeof event.preventDefault); // "function"
// ここでpreventDefaultを呼ばないと、ページが更新される
};
return (
<form onSubmit={handleSubmit}>
<input type="text" />
<button type="submit">送信</button>
</form>
);
}
handleSubmit
関数の引数event
に、イベントに関する詳細な情報が入っています。
その中にpreventDefault
という関数があるんです。
preventDefaultの正しい使い方
では、正しい使い方を見てみましょう。
// ✅ 正しいフォーム処理
function ContactForm() {
const [formData, setFormData] = useState({
username: '',
email: ''
});
const handleSubmit = (event) => {
// 重要:最初にpreventDefaultを呼ぶ
event.preventDefault();
console.log('送信データ:', formData);
// ここでフォームデータの処理
// API呼び出し、バリデーション等
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.username}
onChange={(e) => setFormData({...formData, username: e.target.value})}
placeholder="ユーザー名"
/>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
placeholder="メールアドレス"
/>
<button type="submit">送信</button>
</form>
);
}
event.preventDefault();
を最初に書くのがポイントです!
これで、ページが更新されることなく、Reactでフォームを処理できるようになります。
どんな時にpreventDefaultが必要?
フォーム送信以外にも、preventDefaultが必要な場面があります。
フォーム送信の場合
- イベント:
onSubmit
- デフォルト動作: ページの更新・リロード
- preventDefault: 必須
リンククリックの場合
- イベント:
onClick
- デフォルト動作: リンク先への移動
- preventDefault: SPA内で独自処理する場合
キーボード入力の場合
- イベント:
onKeyDown
- デフォルト動作: 文字の入力、ショートカット実行
- preventDefault: カスタムショートカットを実装する場合
Reactでは、特にフォーム送信で使うことが多いですね。
よくある間違いパターンを知って、同じ失敗を避けよう
フォーム処理でよく発生する間違いとその解決方法をご紹介します。 初心者の方がよくハマるポイントなので、ぜひチェックしてくださいね。
1. preventDefaultを呼び忘れている
これが一番多い間違いです。
// ❌ preventDefaultを呼んでいない
function LoginForm() {
const [credentials, setCredentials] = useState({
email: '',
password: ''
});
const handleSubmit = (event) => {
// preventDefault() を呼んでいない!
console.log('ログイン試行:', credentials);
// この後、ページが更新されてしまう
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={credentials.email}
onChange={(e) => setCredentials({...credentials, email: e.target.value})}
/>
<input
type="password"
value={credentials.password}
onChange={(e) => setCredentials({...credentials, password: e.target.value})}
/>
<button type="submit">ログイン</button>
</form>
);
}
正しい書き方はこちらです。
// ✅ 正しい実装
function LoginForm() {
const [credentials, setCredentials] = useState({
email: '',
password: ''
});
const handleSubmit = (event) => {
event.preventDefault(); // これが重要!
console.log('ログイン試行:', credentials);
// ここでログイン処理
authenticateUser(credentials);
};
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={credentials.email}
onChange={(e) => setCredentials({...credentials, email: e.target.value})}
/>
<input
type="password"
value={credentials.password}
onChange={(e) => setCredentials({...credentials, password: e.target.value})}
/>
<button type="submit">ログイン</button>
</form>
);
}
event.preventDefault();
この一行を忘れずに!
2. eventオブジェクトを受け取っていない
関数の引数でevent
を受け取ることを忘れがちです。
// ❌ eventパラメータがない
function SearchForm() {
const [query, setQuery] = useState('');
const handleSubmit = () => { // eventパラメータがない
// event.preventDefault(); // これはエラーになる
console.log('検索:', query);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
<button type="submit">検索</button>
</form>
);
}
正しくは、こうです。
// ✅ 正しい実装
function SearchForm() {
const [query, setQuery] = useState('');
const handleSubmit = (event) => { // eventパラメータを受け取る
event.preventDefault();
console.log('検索:', query);
// 検索処理の実装
performSearch(query);
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="検索キーワード"
/>
<button type="submit">検索</button>
</form>
);
}
関数の引数で(event)
を忘れずに書きましょう。
3. ボタンのtypeを間違えている
ボタンのtype
属性も重要なポイントです。
// ❌ ボタンのtype指定が間違っている
function CommentForm() {
const [comment, setComment] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
console.log('コメント:', comment);
};
return (
<form onSubmit={handleSubmit}>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
{/* type="button" だとフォーム送信されない */}
<button type="button" onClick={handleSubmit}>送信</button>
</form>
);
}
正しい方法は2つあります。
方法1:type="submit"を使う
// ✅ 正しい実装(方法1)
function CommentForm() {
const [comment, setComment] = useState('');
const handleSubmit = (event) => {
event.preventDefault();
console.log('コメント:', comment);
};
return (
<form onSubmit={handleSubmit}>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
{/* type="submit" でフォーム送信される */}
<button type="submit">送信</button>
</form>
);
}
方法2:onClickハンドラを使う
// ✅ 正しい実装(方法2)
function CommentForm() {
const [comment, setComment] = useState('');
const handleClick = (event) => {
event.preventDefault();
console.log('コメント:', comment);
};
return (
<form>
<textarea
value={comment}
onChange={(e) => setComment(e.target.value)}
/>
{/* type="button" + onClickで明示的に処理 */}
<button type="button" onClick={handleClick}>送信</button>
</form>
);
}
どちらでも動きますが、フォームの場合は方法1のtype="submit"
がおすすめです。
4. 非同期処理での落とし穴
非同期処理(async/await)を使う場合の注意点もあります。
// ❌ 非同期処理で問題が起こる例
function RegistrationForm() {
const [userData, setUserData] = useState({
name: '',
email: '',
password: ''
});
const handleSubmit = async (event) => {
// preventDefault を await の後に置くのは危険
try {
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
// ここでpreventDefaultを呼んでも遅い
event.preventDefault(); // この時点では既にページが更新されている可能性
const result = await response.json();
console.log('登録成功:', result);
} catch (error) {
console.error('登録エラー:', error);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={userData.name}
onChange={(e) => setUserData({...userData, name: e.target.value})}
/>
<input
type="email"
value={userData.email}
onChange={(e) => setUserData({...userData, email: e.target.value})}
/>
<input
type="password"
value={userData.password}
onChange={(e) => setUserData({...userData, password: e.target.value})}
/>
<button type="submit">登録</button>
</form>
);
}
正しくは、最初にpreventDefaultを呼ぶことが重要です。
// ✅ 正しい実装
function RegistrationForm() {
const [userData, setUserData] = useState({
name: '',
email: '',
password: ''
});
const [isSubmitting, setIsSubmitting] = useState(false);
const handleSubmit = async (event) => {
// 最初にpreventDefaultを呼ぶ
event.preventDefault();
if (isSubmitting) return; // 重複送信防止
try {
setIsSubmitting(true);
const response = await fetch('/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(userData)
});
const result = await response.json();
if (response.ok) {
console.log('登録成功:', result);
// 成功時の処理(リダイレクトなど)
} else {
throw new Error(result.message);
}
} catch (error) {
console.error('登録エラー:', error);
alert('登録に失敗しました: ' + error.message);
} finally {
setIsSubmitting(false);
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={userData.name}
onChange={(e) => setUserData({...userData, name: e.target.value})}
placeholder="名前"
required
/>
<input
type="email"
value={userData.email}
onChange={(e) => setUserData({...userData, email: e.target.value})}
placeholder="メールアドレス"
required
/>
<input
type="password"
value={userData.password}
onChange={(e) => setUserData({...userData, password: e.target.value})}
placeholder="パスワード"
required
/>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '登録中...' : '登録'}
</button>
</form>
);
}
event.preventDefault();
は、関数の一番最初に書くのがポイントです。
非同期処理の前に必ず呼びましょう!
実際のアプリで使える!実践的なフォーム実装例
実際のアプリケーションでよく使用されるフォームパターンをご紹介します。 ここまで理解できたら、より実践的なものにチャレンジしてみましょう。
バリデーション付きフォーム
実際のアプリでは、入力内容をチェックする機能が必要ですよね。
// バリデーション機能付きのフォーム
function ValidatedForm() {
const [formData, setFormData] = useState({
email: '',
password: '',
confirmPassword: ''
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
// バリデーション関数
const validateForm = () => {
const newErrors = {};
// メールアドレスの検証
if (!formData.email) {
newErrors.email = 'メールアドレスは必須です';
} else if (!/\S+@\S+\.\S+/.test(formData.email)) {
newErrors.email = '有効なメールアドレスを入力してください';
}
// パスワードの検証
if (!formData.password) {
newErrors.password = 'パスワードは必須です';
} else if (formData.password.length < 8) {
newErrors.password = 'パスワードは8文字以上で入力してください';
}
// パスワード確認の検証
if (formData.password !== formData.confirmPassword) {
newErrors.confirmPassword = 'パスワードが一致しません';
}
return newErrors;
};
const handleSubmit = async (event) => {
event.preventDefault();
// バリデーション実行
const validationErrors = validateForm();
setErrors(validationErrors);
// エラーがある場合は送信しない
if (Object.keys(validationErrors).length > 0) {
return;
}
try {
setIsSubmitting(true);
// API呼び出し
const response = await fetch('/api/signup', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: formData.email,
password: formData.password
})
});
if (response.ok) {
alert('登録が完了しました');
// フォームリセット
setFormData({ email: '', password: '', confirmPassword: '' });
} else {
const error = await response.json();
throw new Error(error.message);
}
} catch (error) {
alert('エラー: ' + error.message);
} finally {
setIsSubmitting(false);
}
};
const handleInputChange = (field) => (event) => {
setFormData({
...formData,
[field]: event.target.value
});
// エラーをクリア
if (errors[field]) {
setErrors({
...errors,
[field]: ''
});
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="email">メールアドレス</label>
<input
id="email"
type="email"
value={formData.email}
onChange={handleInputChange('email')}
className={errors.email ? 'error' : ''}
/>
{errors.email && (
<span className="error-message">{errors.email}</span>
)}
</div>
<div>
<label htmlFor="password">パスワード</label>
<input
id="password"
type="password"
value={formData.password}
onChange={handleInputChange('password')}
className={errors.password ? 'error' : ''}
/>
{errors.password && (
<span className="error-message">{errors.password}</span>
)}
</div>
<div>
<label htmlFor="confirmPassword">パスワード確認</label>
<input
id="confirmPassword"
type="password"
value={formData.confirmPassword}
onChange={handleInputChange('confirmPassword')}
className={errors.confirmPassword ? 'error' : ''}
/>
{errors.confirmPassword && (
<span className="error-message">{errors.confirmPassword}</span>
)}
</div>
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? '送信中...' : '登録'}
</button>
</form>
);
}
長いコードですが、ポイントを見ていきましょう。
バリデーション関数では、入力内容をチェックしてエラーメッセージを返しています。 メールアドレスの形式チェックや、パスワードの長さチェックを行っています。
handleSubmit関数では、まずevent.preventDefault()
を呼んでから、バリデーションを実行しています。
エラーがあれば送信せず、エラーがなければAPI呼び出しを行います。
エラー表示では、各入力フィールドの下にエラーメッセージを表示しています。 ユーザーが入力を修正すると、エラーが自動的にクリアされます。
複数ステップのフォーム
大きなフォームは、複数のステップに分けると使いやすくなります。
// 複数ステップに分かれたフォーム
function MultiStepForm() {
const [currentStep, setCurrentStep] = useState(1);
const [formData, setFormData] = useState({
// ステップ1
personalInfo: {
firstName: '',
lastName: '',
email: ''
},
// ステップ2
address: {
street: '',
city: '',
zipCode: ''
},
// ステップ3
preferences: {
newsletter: false,
notifications: true
}
});
const handleSubmit = async (event) => {
event.preventDefault();
if (currentStep < 3) {
// 次のステップに進む
setCurrentStep(currentStep + 1);
} else {
// 最終送信
try {
const response = await fetch('/api/complete-registration', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(formData)
});
if (response.ok) {
alert('登録が完了しました!');
}
} catch (error) {
alert('エラーが発生しました');
}
}
};
const handlePrevious = (event) => {
event.preventDefault(); // ボタンのデフォルト動作を防ぐ
if (currentStep > 1) {
setCurrentStep(currentStep - 1);
}
};
const updateFormData = (section, field, value) => {
setFormData({
...formData,
[section]: {
...formData[section],
[field]: value
}
});
};
const renderStep = () => {
switch (currentStep) {
case 1:
return (
<div>
<h2>個人情報</h2>
<input
type="text"
placeholder="姓"
value={formData.personalInfo.firstName}
onChange={(e) => updateFormData('personalInfo', 'firstName', e.target.value)}
/>
<input
type="text"
placeholder="名"
value={formData.personalInfo.lastName}
onChange={(e) => updateFormData('personalInfo', 'lastName', e.target.value)}
/>
<input
type="email"
placeholder="メールアドレス"
value={formData.personalInfo.email}
onChange={(e) => updateFormData('personalInfo', 'email', e.target.value)}
/>
</div>
);
case 2:
return (
<div>
<h2>住所情報</h2>
<input
type="text"
placeholder="住所"
value={formData.address.street}
onChange={(e) => updateFormData('address', 'street', e.target.value)}
/>
<input
type="text"
placeholder="市区町村"
value={formData.address.city}
onChange={(e) => updateFormData('address', 'city', e.target.value)}
/>
<input
type="text"
placeholder="郵便番号"
value={formData.address.zipCode}
onChange={(e) => updateFormData('address', 'zipCode', e.target.value)}
/>
</div>
);
case 3:
return (
<div>
<h2>設定</h2>
<label>
<input
type="checkbox"
checked={formData.preferences.newsletter}
onChange={(e) => updateFormData('preferences', 'newsletter', e.target.checked)}
/>
ニュースレターを受け取る
</label>
<label>
<input
type="checkbox"
checked={formData.preferences.notifications}
onChange={(e) => updateFormData('preferences', 'notifications', e.target.checked)}
/>
通知を受け取る
</label>
</div>
);
default:
return null;
}
};
return (
<form onSubmit={handleSubmit}>
<div className="progress">
ステップ {currentStep} / 3
</div>
{renderStep()}
<div className="form-actions">
{currentStep > 1 && (
<button type="button" onClick={handlePrevious}>
戻る
</button>
)}
<button type="submit">
{currentStep < 3 ? '次へ' : '送信'}
</button>
</div>
</form>
);
}
この例では、現在のステップを管理して、段階的にフォームを進めています。
handleSubmit関数では、最後のステップでない場合は次のステップに進み、最後のステップでは実際にデータを送信しています。
handlePrevious関数では、event.preventDefault()
を呼んで、前のステップに戻っています。
updateFormData関数では、ネストした状態を更新しています。 これで、各ステップのデータを適切に管理できます。
ファイルアップロード付きフォーム
ファイルアップロード機能も実装してみましょう。
// ファイルアップロード機能付きフォーム
function FileUploadForm() {
const [formData, setFormData] = useState({
title: '',
description: '',
file: null
});
const [uploadProgress, setUploadProgress] = useState(0);
const [isUploading, setIsUploading] = useState(false);
const handleFileChange = (event) => {
const file = event.target.files[0];
setFormData({
...formData,
file: file
});
};
const handleSubmit = async (event) => {
event.preventDefault();
if (!formData.file) {
alert('ファイルを選択してください');
return;
}
try {
setIsUploading(true);
setUploadProgress(0);
// FormData を使用してファイルを送信
const formDataToSend = new FormData();
formDataToSend.append('title', formData.title);
formDataToSend.append('description', formData.description);
formDataToSend.append('file', formData.file);
// XMLHttpRequest を使用してアップロード進捗を追跡
const xhr = new XMLHttpRequest();
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
const progress = Math.round((event.loaded / event.total) * 100);
setUploadProgress(progress);
}
});
xhr.addEventListener('load', () => {
if (xhr.status === 200) {
alert('アップロードが完了しました!');
// フォームリセット
setFormData({ title: '', description: '', file: null });
setUploadProgress(0);
} else {
throw new Error('アップロードに失敗しました');
}
});
xhr.addEventListener('error', () => {
throw new Error('ネットワークエラーが発生しました');
});
xhr.open('POST', '/api/upload');
xhr.send(formDataToSend);
} catch (error) {
alert('エラー: ' + error.message);
} finally {
setIsUploading(false);
}
};
return (
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="title">タイトル</label>
<input
id="title"
type="text"
value={formData.title}
onChange={(e) => setFormData({...formData, title: e.target.value})}
required
/>
</div>
<div>
<label htmlFor="description">説明</label>
<textarea
id="description"
value={formData.description}
onChange={(e) => setFormData({...formData, description: e.target.value})}
/>
</div>
<div>
<label htmlFor="file">ファイル</label>
<input
id="file"
type="file"
onChange={handleFileChange}
accept="image/*,application/pdf"
required
/>
{formData.file && (
<p>選択されたファイル: {formData.file.name}</p>
)}
</div>
{isUploading && (
<div className="upload-progress">
<div className="progress-bar">
<div
className="progress-fill"
style={{ width: `${uploadProgress}%` }}
/>
</div>
<span>{uploadProgress}%</span>
</div>
)}
<button type="submit" disabled={isUploading}>
{isUploading ? 'アップロード中...' : 'アップロード'}
</button>
</form>
);
}
ファイルアップロードでは、FormDataという特殊なオブジェクトを使います。
handleFileChange関数では、選択されたファイルを状態に保存しています。
handleSubmit関数では、event.preventDefault()
を呼んでから、FormDataを作成してサーバーに送信しています。
アップロード進捗も表示できるように、XMLHttpRequestを使って進捗を追跡しています。 ユーザーにとって親切ですよね。
困った時のデバッグ方法を覚えよう
フォーム送信の問題を効率的にデバッグする方法をご紹介します。 「なんかうまくいかない...」という時に役立ちますよ。
デバッグの基本ステップ
まず、問題の原因を特定するために、コンソールログを使って状況を確認しましょう。
// デバッグ用のコンソールログ
function DebugForm() {
const [formData, setFormData] = useState({ name: '', email: '' });
const handleSubmit = (event) => {
console.log('=== フォーム送信デバッグ ===');
console.log('1. handleSubmit が呼ばれました');
console.log('2. event オブジェクト:', event);
console.log('3. event.type:', event.type);
console.log('4. event.target:', event.target);
// preventDefault を呼ぶ前の状態を確認
console.log('5. preventDefault を呼ぶ前');
event.preventDefault();
console.log('6. preventDefault を呼んだ後');
console.log('7. フォームデータ:', formData);
// この後に実際の処理
console.log('8. 実際の処理を開始');
};
const handleInputChange = (field) => (event) => {
console.log(`${field} が変更されました:`, event.target.value);
setFormData({
...formData,
[field]: event.target.value
});
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={formData.name}
onChange={handleInputChange('name')}
placeholder="名前"
/>
<input
type="email"
value={formData.email}
onChange={handleInputChange('email')}
placeholder="メール"
/>
<button type="submit">送信</button>
</form>
);
}
このように、各ステップでconsole.logを入れることで、どこで問題が起きているかがわかります。
ブラウザの開発者ツール(F12キー)のコンソールタブを見ながら、フォームを送信してみてください。
よくある問題の診断方法
ページが更新される場合
- 原因:
preventDefault()
を呼んでいない、eventオブジェクトを受け取っていない - 確認方法: console.logでイベントハンドラが呼ばれているか確認、eventオブジェクトの存在確認
フォームデータが送信されない場合
- 原因: inputのvalueとonChangeが正しく設定されていない、状態更新のタイミングの問題
- 確認方法: React Developer Toolsで状態を確認、Networkタブで API呼び出しを確認
バリデーションが動作しない場合
- 原因: バリデーション関数の実装ミス、エラー状態の管理が不適切
- 確認方法: バリデーション関数を単体でテスト、エラー状態をコンソールで確認
実際の診断用コンポーネント
本格的なデバッグ用のコンポーネントも作ってみました。
// 実際の診断用コンポーネント
function DiagnosticForm() {
const [formData, setFormData] = useState({ email: '', password: '' });
const [diagnostics, setDiagnostics] = useState([]);
const addDiagnostic = (message) => {
const timestamp = new Date().toLocaleTimeString();
setDiagnostics(prev => [
...prev,
{ timestamp, message }
]);
};
const handleSubmit = (event) => {
addDiagnostic('handleSubmit が呼ばれました');
if (!event) {
addDiagnostic('❌ event オブジェクトが undefined です');
return;
} else {
addDiagnostic('✅ event オブジェクトを受け取りました');
}
if (typeof event.preventDefault !== 'function') {
addDiagnostic('❌ preventDefault が利用できません');
return;
} else {
addDiagnostic('✅ preventDefault を実行します');
event.preventDefault();
addDiagnostic('✅ preventDefault を実行しました');
}
addDiagnostic(`📋 フォームデータ: ${JSON.stringify(formData)}`);
// バリデーション確認
if (!formData.email || !formData.password) {
addDiagnostic('❌ 必須項目が入力されていません');
return;
} else {
addDiagnostic('✅ バリデーション通過');
}
addDiagnostic('🚀 送信処理を開始します');
};
return (
<div>
<form onSubmit={handleSubmit}>
<input
type="email"
value={formData.email}
onChange={(e) => setFormData({...formData, email: e.target.value})}
placeholder="メールアドレス"
/>
<input
type="password"
value={formData.password}
onChange={(e) => setFormData({...formData, password: e.target.value})}
placeholder="パスワード"
/>
<button type="submit">送信</button>
</form>
<div style={{ marginTop: '20px', border: '1px solid #ccc', padding: '10px' }}>
<h3>診断ログ</h3>
<button onClick={() => setDiagnostics([])}>ログクリア</button>
<div style={{ height: '200px', overflow: 'auto', fontSize: '12px' }}>
{diagnostics.map((log, index) => (
<div key={index}>
<strong>{log.timestamp}</strong>: {log.message}
</div>
))}
</div>
</div>
</div>
);
}
この診断用コンポーネントでは、フォーム送信の各ステップを詳細にログに記録しています。
addDiagnostic
関数で、タイムスタンプ付きのメッセージを記録しています。
handleSubmit
関数では、eventオブジェクトの確認、preventDefaultの実行、フォームデータの確認、バリデーションの確認を順番に行い、それぞれの結果をログに出力しています。
問題が起きた時は、このログを見ることで、どこで問題が発生しているかがすぐにわかります。
まとめ:Reactのフォーム処理をマスターしよう!
お疲れ様でした! Reactでのフォーム送信とpreventDefaultの使い方について、詳しく学んできました。
重要なポイントをもう一度おさらいしましょう
- event.preventDefault()を忘れずに:フォーム送信時は必ず最初に呼ぶ
- eventオブジェクトを受け取る:関数の引数で(event)を忘れずに
- 非同期処理でも最初にpreventDefault:awaitの前に必ず呼ぶ
- ボタンのtype属性:submitかbuttonか、用途に応じて使い分ける
実践的なスキルも身につきました
- バリデーション機能付きフォーム
- 複数ステップのフォーム
- ファイルアップロード機能
- デバッグとトラブルシューティング
preventDefaultは、Reactでフォーム処理をする上で絶対に欠かせない基本中の基本です。 最初は「なんで必要なの?」と思うかもしれませんが、ブラウザのデフォルト動作とReactの仕組みを理解すれば、必要性がよくわかりますよね。
問題が発生した時は、段階的なデバッグを行うことで、効率的に原因を特定できます。 コンソールログをうまく活用して、一歩ずつ問題を解決していきましょう。
ぜひ今回の内容を参考に、正しいフォーム処理を実装して、使いやすいWebアプリケーションを作ってみてくださいね!