
カスタムフックの基本概念
カスタムフックは、Reactの組み込みフック(useState
やuseEffect
など)を組み合わせて、特定の機能を持つ再利用可能な関数です。カスタムフックは、名前がuse
で始まり、コンポーネントや他のフック内で呼び出されます。ロジックをコンポーネントから分離することで、コードの重複を減らし、テストやメンテナンスが容易になります。基本的なカスタムフックの例は以下の通りです。
import { useState } from 'react';
function useCounter() {
const [count, setCount] = useState(0);
const increment = () => setCount(count + 1);
const decrement = () => setCount(count - 1);
return { count, increment, decrement };
}
このuseCounter
フックは、カウンターの状態と操作を提供し、複数のコンポーネントで再利用できます。
ハンズオン:カスタムフックでフォーム入力を管理
カスタムフックの基本を理解するため、フォーム入力を管理するカスタムフックを作成します。第1回で作成したプロジェクト(my-react-app)を使用します。まず、src/useForm.js
を作成してカスタムフックを定義します。
import { useState } from 'react';
function useForm(initialValues) {
const [values, setValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value } = e.target;
setValues((prev) => ({
...prev,
[name]: value,
}));
};
const resetForm = () => {
setValues(initialValues);
};
return { values, handleChange, resetForm };
}
export default useForm;
このuseForm
フックは、フォームの入力値を管理し、変更ハンドリングとリセット機能を提供します。次に、src/App.js
を以下のように編集して、カスタムフックを使用したフォームを実装します。
import React, { useState } from 'react';
import useForm from './useForm';
import './App.css';
function App() {
const { values, handleChange, resetForm } = useForm({
name: '',
email: '',
});
const [message, setMessage] = useState('');
const handleSubmit = () => {
if (values.name.trim() && values.email.trim()) {
setMessage(`こんにちは、${values.name}さん!メール: ${values.email}`);
resetForm();
} else {
setMessage('すべてのフィールドを入力してください。');
}
};
return (
<div className="app-container">
<h1>カスタムフックでフォーム管理</h1>
<div>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
placeholder="名前を入力"
/>
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
placeholder="メールアドレスを入力"
/>
<button onClick={handleSubmit}>送信</button>
<button onClick={resetForm}>リセット</button>
</div>
<p>{message}</p>
</div>
);
}
export default App;
このコードでは、useForm
フックを使用してフォームの状態とハンドリングを管理します。resetForm
でフォームを初期状態に戻し、handleSubmit
で入力値を処理します。src/App.css
に以下のスタイルを適用します(第8回と同様)。
.app-container {
text-align: center;
padding: 20px;
font-family: Arial, sans-serif;
}
input {
padding: 8px;
margin: 10px;
font-size: 16px;
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
margin: 5px;
}
開発サーバー(npm start
)が起動している状態で保存すると、ブラウザにフォームが表示されます。名前とメールアドレスを入力して「送信」をクリックするとメッセージが表示され、「リセット」をクリックするとフォームがクリアされます。この動作は、useForm
フックがフォームロジックを効率的に管理していることを示しています。
カスタムフックでデータ取得を管理
カスタムフックは、副作用の管理にも役立ちます。APIデータ取得を管理するカスタムフックを作成します。src/useFetch.js
を作成します。
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
setLoading(true);
fetch(url)
.then((response) => response.json())
.then((data) => {
setData(data);
setLoading(false);
})
.catch((err) => {
setError(err.message);
setLoading(false);
});
}, [url]);
return { data, loading, error };
}
export default useFetch;
このuseFetch
フックは、指定されたURLからデータを取得し、データ、読み込み状態、エラーを返します。App.js
を以下のように変更して、useFetch
を使用します。
import React from 'react';
import useFetch from './useFetch';
import './App.css';
function App() {
const { data, loading, error } = useFetch('https://jsonplaceholder.typicode.com/users');
return (
<div className="app-container">
<h1>データ取得のカスタムフック</h1>
{loading && <p>読み込み中...</p>}
{error && <p>エラー: {error}</p>}
{data && (
<ul>
{data.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</div>
);
}
export default App;
App.css
にリストのスタイルを追加します(第7回と同様)。
ul {
list-style: none;
padding: 0;
}
li {
margin: 10px 0;
padding: 10px;
background-color: #f0f0f0;
border-radius: 5px;
max-width: 300px;
margin-left: auto;
margin-right: auto;
}
ブラウザで確認すると、JSONPlaceholderから取得したユーザーリストが表示されます。読み込み中は「読み込み中...」、エラーがあればエラーメッセージが表示されます。useFetch
フックにより、データ取得ロジックが再利用可能になっています。
ハンズオン:カスタムフックの拡張
カスタムフックの柔軟性を体感するため、読者自身で機能を追加します。以下の課題を試してください。useForm.js
を編集し、入力値のバリデーションを追加する機能を組み込みます。以下は一例です。
import { useState } from 'react';
function useForm(initialValues, validate) {
const [values, setValues] = useState(initialValues);
const [errors, setErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setValues((prev) => ({
...prev,
[name]: value,
}));
if (validate) {
const validationErrors = validate({ ...values, [name]: value });
setErrors(validationErrors);
}
};
const resetForm = () => {
setValues(initialValues);
setErrors({});
};
return { values, handleChange, resetForm, errors };
}
export default useForm;
App.js
を以下のように変更してバリデーションを実装します。
import React, { useState } from 'react';
import useForm from './useForm';
import './App.css';
function App() {
const validate = (values) => {
const errors = {};
if (!values.name.trim()) errors.name = '名前は必須です。';
if (!values.email.includes('@')) errors.email = '有効なメールアドレスを入力してください。';
return errors;
};
const { values, handleChange, resetForm, errors } = useForm(
{ name: '', email: '' },
validate
);
const [message, setMessage] = useState('');
const handleSubmit = () => {
if (!errors.name && !errors.email && values.name.trim() && values.email.trim()) {
setMessage(`こんにちは、${values.name}さん!メール: ${values.email}`);
resetForm();
} else {
setMessage('フォームにエラーがあります。');
}
};
return (
<div className="app-container">
<h1>バリデーション付きフォーム</h1>
<div>
<input
type="text"
name="name"
value={values.name}
onChange={handleChange}
placeholder="名前を入力"
/>
{errors.name && <p style={{ color: 'red' }}>{errors.name}</p>}
<input
type="email"
name="email"
value={values.email}
onChange={handleChange}
placeholder="メールアドレスを入力"
/>
{errors.email && <p style={{ color: 'red' }}>{errors.email}</p>}
<button onClick={handleSubmit}>送信</button>
<button onClick={resetForm}>リセット</button>
</div>
<p>{message}</p>
</div>
);
}
export default App;
このコードでは、useForm
にバリデーション機能を追加し、エラーメッセージを表示します。ブラウザで確認すると、名前が空またはメールアドレスが無効な場合にエラーメッセージが表示されます。自分でバリデーションルール(例:名前の長さ制限)や新しいカスタムフック(例:トグルスイッチ用)を追加し、カスタムフックの可能性を試してください。
次のステップに向けて
本記事では、カスタムフックを設計し、フォーム管理やデータ取得のロジックを再利用可能にしました。カスタムフックは、コードのモジュール性と再利用性を高める強力なツールです。第12回では、Reactアプリケーションのパフォーマンス最適化と本番環境へのデプロイ方法を学び、完成したアプリケーションを公開します。これにより、実際のプロジェクトに応用可能なスキルが完成します。