Reactでよくあるエラー3つと現場で学んだ対処法
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
最新ツール・トレンド情報をずんだもん×めたんが解説するYouTubeチャンネルを運営しています!
いいね、チャンネル登録してもらえると嬉しいです🙇♂️
ハジメル.dev: https://hajimeru-dev.vercel.app/
「ひとりで続けるのは難しい」「何から学べばいいか分からない」という方向けに、
プログラミングのマンツーマンレッスンサービス「ハジメル.dev」も運営しています。
未経験OK・オンライン完結・月額制/違約金なしなので、気軽に無料相談してみてください🙇♂️
海外テックニュースを追いたいけど、英語や情報量の多さで大変…という方向けに、
Hacker News の話題を日本語でサクッと追える「HackerNews 日本語まとめ & AI要約」
を個人開発しました!
技術トレンド収集に使ってもらえると嬉しいです🔥🙇♂️
→ HackerNews 日本語まとめ & AI要約: https://hn-matome-2ht.pages.dev/
「ニャンパイアサバイバー」というヴァンパイアサバイバーリスペクトのゲームを作成しました!
もしよろしければ遊んで頂けると嬉しいです😭
習い事教室の先生向けに、SNS 投稿・生徒募集・保護者通知の文章を AI で生成する Web サービス「おしらせAI」を個人開発しました。Next.js + Supabase + LLM で構成しており、無料で月 10 回まで試用できます。よければ触ってみてください。
→ おしらせAI: https://oshirase-ai.vercel.app/
✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨✨
Reactコンポーネントのライフサイクルでハマった初心者の悩み
Reactを始めて3ヶ月目のエンジニアとして、チームのフロントエンド開発に携わった時の体験を振り返ってみられます。最初はチュートリアルのコードを真似て問題なく進められましたが、実際のプロジェクトで初めてコンポーネントのライフサイクルを扱う際は様々な壁にぶち当たっていました。
特に明確に記憶しているのが、ユーザー認証画面を開発していた際のエラー事例です。ログインボタンを押下すると、バックエンドにPOSTリクエストを送る処理をuseEffectで実装していたのですが、エラーが出てログインできない状態になってしまいました。その時のコードは以下の通りでした。
import React, { useEffect } from 'react';
function LoginScreen() {
useEffect(() => {
fetch('/api/login', {
method: 'POST',
body: JSON.stringify({ username: 'test', password: 'password' })
})
.then(response => response.json())
.then(data => {
console.log('Login success:', data);
})
.catch(error => {
console.error('Login failed:', error);
});
}, []);
return (
<button onClick={() => {}}>Login</button>
);
}
このコードで起きた問題は、リクエストのタイミングが正しくないことでした。useEffectはコンポーネントがマウントされたときに一度だけ実行されるため、ボタンがクリックされたタイミングで実行する必要がありますでした。これを修正するには、useEffectの依存配列に空の配列を渡さずに、ボタンクリックのハンドラ内で直接非同期処理を書く形に変更しました。
const handleLogin = async () => {
try {
const response = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username: 'test', password: 'password' })
});
const data = await response.json();
console.log('Login success:', data);
} catch (error) {
console.error('Login failed:', error);
}
};
この変更によって、ボタンクリックのタイミングでリクエストが送信されるようになり、ログインが成功するようになりました。この問題を通じて、Reactのコンポーネントライフサイクルの理解が深まり、不要なサイドエフェクトを防ぐための設計の重要性を実感しました。
Reactでのフォーム検証の落とし穴と改善策
次にハマったのが、ユーザー登録画面でのフォーム検証の実装です。当初はHTMLのinput要素のrequired属性を使用していましたが、現場ではバックエンドとの整合性を保つためにReact内で検証ロジックを自分で実装する必要がありました。
実装したコードは以下のような形でした。
const [formData, setFormData] = useState({
username: '',
password: '',
passwordConfirmation: ''
});
const validateForm = () => {
if (!formData.username) {
alert('ユーザー名は必須です');
return false;
}
if (formData.password !== formData.passwordConfirmation) {
alert('パスワードと確認パスワードが一致しません');
return false;
}
return true;
};
しかし、このコードで発生した問題は、フォームが空白の状態で送信されてしまうことです。submitイベントがdispatch_actionしてしまうことで、検証前のデータが送信されてしまうため、バックエンドでもエラーが発生してしまいます。
この問題を解決するために、フォームの送信処理を検証ロジックに依存させる形に変更しました。
const handleSubmit = (e) => {
e.preventDefault();
if (!validateForm()) {
return;
}
// 送信処理
fetch('/api/register', {
method: 'POST',
body: JSON.stringify(formData)
})
.then(response => {
// 成功時の処理
})
.catch(error => {
// エラー時の処理
});
};
この変更によって、検証がパスしないとフォームが送信されなくなり、バックエンドエラーを防ぐことができました。この経験から、Reactのフォーム検証には単純なチェックだけでなく、送信フロー全体を考慮する必要があることを学びました。
コンポーネントの再利用性を高めるための設計方針
その後、チーム内でコンポーネントの再利用性を高めるための設計について考え始めました。当初は各コンポーネントを孤立して開発していましたが、プロジェクトが進むにつれて、同じ機能を複数箇所で実装する必要が出てきました。
例えば、ボタンコンポーネントは、スタイルや振る舞いが異なる場所で複数必要になりました。最初は以下のような形で実装していました。
const PrimaryButton = ({ children }) => {
return (
<button className="btn-primary">
{children}
</button>
);
};
const SecondaryButton = ({ children }) => {
return (
<button className="btn-secondary">
{children}
</button>
);
};
しかし、この設計ではシンボルが多くなり、スタイルの変更が困難になってしまいます。そこで、propsを使ってスタイルや振る舞いをカスタマイズできるコンポーネントに改修しました。
const Button = ({
children,
variant = 'primary',
type = 'button'
}) => {
const classes = ['btn'];
if (variant === 'primary') {
classes.push('btn-primary');
} else if (variant === 'secondary') {
classes.push('btn-secondary');
}
return (
<button className={classes.join(' ')} type={type}>
{children}
</button>
);
};
この改修によって、必要に応じてスタイルを変更できる柔軟なコンポーネントが実現できました。また、typeプロップを指定することで、ボタン以外の要素としても利用できるようになり、コンポーネントの再利用性が大幅に向上しました。
Reactの型チェックの重要性とTypeScriptの導入
プロジェクトが進むにつれて、型のミスによるバグが増えてきました。特にAPIレスポンスの型が一致しない場合や、プロップスの型が一致しない場合に、難しいバグが発生していました。
例えば、以下のようなコードが存在していたのです。
function UserProfile({ user }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.age}</p>
</div>
);
}
このコードで発生していた問題は、user.ageが実際に数値であるかどうかの型チェックがされていないことです。もしもどこかでstringとして渡ってしまった場合、NaNになってしまう可能性があります。
この問題を解決するために、TypeScriptを導入しました。型定義を追加することで、コンパイル時にエラーを検出できるようになりました。
interface User {
name: string;
age: number;
}
function UserProfile({ user }: { user: User }) {
return (
<div>
<h1>{user.name}</h1>
<p>{user.age}</p>
</div>
);
}
この変更によって、型ミスを早い段階で検出できるようになり、バグの発生頻度が大幅に減少しました。また、チームメンバー間でのコードの理解がしやすくなり、メンテナンス性も向上しました。
現場で役立ったReactのベストプラクティス
最後に、現場で役立ったReactのベストプラクティスについてまとめます。実際の開発では、理論だけでなく、現場で役立つ実践的な知識が重要です。
- コンポーネントの分離:UIの小さなユニットを独立したコンポーネントに分割する
- プロップスの型チェック:TypeScriptやプロップスの検証ライブラリを使う
- エラーハンドリング:エラー境界を使ってエラーを適切に処理する
- コードの再利用性:高い再利用性を持つコンポーネントを設計する
- パフォーマンス最適化:不要な再レンダリングを防ぐためのshouldComponentUpdateやReact.memoを使う
これらの実践をチーム内で定着させることで、開発効率が上がり、メンテナンス性も向上しました。特にエラーハンドリングは、開発初期から意識することで、運用時のトラブルを大幅に減らすことができます。
まとめ
この記事では、Reactを始めてからの現場での体験談を通じて、よくあるエラーとその対処法を紹介しました。コンポーネントのライフサイクルの理解、フォーム検証の実装、コンポーネントの再利用性、型チェックの重要性などを、具体的なコード例を交えて解説しました。
Reactは学習曲線が急なフレームワークですが、実際の現場で学ぶことで、技術力が大きく向上することが分かりました。今回紹介した経験をもとに、今後の開発にも役立てていきたいと思います。
これからReactを始める皆さんも、現場で学ぶ姿勢を持って、実際のプロジェクトに携わることで、より深い理解が得られると思います。頑張ってください!