島根県安来市のフリーランスエンジニア_プログラマー画像1

React + Firebase で施設予約システムを実装|開発の全記録と実装ポイント

React(TypeScript)とFirebaseを使用して、実際に動作する施設予約システムを開発した際の実装ポイント、つまずきやすい箇所、そして工夫した点を詳しく解説します。

1. 開発した機能一覧

ユーザー認証

Firebase Authenticationによる登録・ログイン機能

予約管理

施設の予約作成・一覧表示・キャンセル機能

重複チェック

同じ日時・施設の二重予約を防止

カレンダー連携

Googleカレンダーへの自動追加機能

データ永続化

Cloud Firestoreによるリアルタイムデータ管理

レスポンシブ対応

モバイル・タブレット・PCで最適表示

使用技術スタック

  • フロントエンド:React 18 + TypeScript
  • ビルドツール:Vite
  • ルーティング:React Router v6
  • バックエンド:Firebase (Authentication, Firestore)
  • ホスティング:Firebase Hosting
  • スタイリング:CSS3(カスタムプロパティ使用)

2. 実装のポイント

2-1. Firebase設定の初期化

Firebaseを使用する際、最初に設定ファイルを適切に初期化することが重要です。

src/firebase.ts
import { initializeApp } from 'firebase/app'; import { getAuth } from 'firebase/auth'; import { getFirestore } from 'firebase/firestore'; const firebaseConfig = { apiKey: process.env.VITE_FIREBASE_API_KEY, authDomain: process.env.VITE_FIREBASE_AUTH_DOMAIN, projectId: process.env.VITE_FIREBASE_PROJECT_ID, // ... その他の設定 }; const app = initializeApp(firebaseConfig); export const auth = getAuth(app); export const db = getFirestore(app);

💡 工夫したポイント

環境変数を使用することで、開発環境と本番環境で異なるFirebaseプロジェクトを使い分けられるようにしました。これにより、テストデータと本番データを分離できます。

2-2. 認証状態の管理

Reactアプリケーション全体で認証状態を管理するため、onAuthStateChangedを使用しました。

src/App.tsx
function App() { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (currentUser) => { setUser(currentUser); setLoading(false); }); return () => unsubscribe(); }, []); if (loading) { return <div className="loading">読み込み中...</div>; } return ( <Router> <Routes> <Route path="/home" element={user ? <Home /> : <Navigate to="/login" />} /> {/* ... その他のルート */} </Routes> </Router> ); }

⚠️ よくある間違い

loadingステートを設けずに実装すると、初回レンダリング時にuserがnullのため、ログイン済みでも強制的にログインページにリダイレクトされてしまいます。必ずloading状態を管理しましょう。

2-3. Firestoreのデータ構造設計

予約システムでは、以下のようなデータ構造を採用しました。

// コレクション構造 group(コレクション) └─ {userId}(ドキュメント) ├─ gname: "グループ名" ├─ mail: "メールアドレス" └─ reserve(サブコレクション) └─ {yyyyMMdd施設コード}(ドキュメント) ├─ date: "20260214" └─ facilityName: "1F午前" reserve(コレクション) └─ {yyyyMMdd施設コード}(ドキュメント) ├─ uid: "ユーザーID" └─ gname: "グループ名"

💡 工夫したポイント

予約IDを「日付 + 施設コード」の組み合わせにすることで、重複チェックが容易になりました。また、groupコレクション配下にreserveサブコレクションを持たせることで、ユーザーごとの予約一覧を効率的に取得できます。

2-4. 予約の重複チェック

同じ日時・施設での二重予約を防ぐため、予約作成前にFirestoreでチェックを行います。

src/components/Reserve.tsx
const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); const formattedDate = formatDate(date); // "20260214" const reserveId = `${formattedDate}${facilityCode}`; // 既存の予約をチェック const reserveRef = doc(db, 'reserve', reserveId); const reserveSnap = await getDoc(reserveRef); if (reserveSnap.exists()) { setError('この日付・施設は既に予約されています'); return; } // 予約を保存 await setDoc(reserveRef, { uid: group.uid, gname: group.gname, }); setSuccess('予約が完了しました!'); };

⚠️ よくある間違い

非同期処理の順序を間違えると、複数のユーザーが同時に予約した際に重複が発生します。必ず「チェック → 保存」の順序を守り、トランザクションの使用も検討しましょう。

3. セキュリティルールの設定

Firestoreのセキュリティルールは非常に重要です。適切に設定しないと、データが不正にアクセスされる可能性があります。

firestore.rules
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // グループコレクション:本人のみアクセス可 match /group/{userId} { allow read, write: if request.auth != null && request.auth.uid == userId; match /reserve/{reserveId} { allow read, write: if request.auth != null && request.auth.uid == userId; } } // 予約コレクション:ログイン済みユーザーは読み取り可 match /reserve/{reserveId} { allow read: if request.auth != null; allow create: if request.auth != null; allow delete: if request.auth != null && resource.data.uid == request.auth.uid; } } }

💡 工夫したポイント

予約の削除は予約を作成した本人のみが実行できるよう、resource.data.uidをチェックしています。これにより、他人の予約を削除できないようになっています。

4. つまずきやすいポイント

4-1. Firebase Authenticationのエラーハンドリング

Firebase Authenticationは、エラーコードが英語で返ってくるため、日本語メッセージに変換する必要があります。

try { await signInWithEmailAndPassword(auth, email, password); } catch (err: any) { if (err.code === 'auth/invalid-credential') { setError('メールアドレスまたはパスワードが正しくありません'); } else if (err.code === 'auth/email-already-in-use') { setError('このメールアドレスは既に登録されています'); } else { setError('ログインに失敗しました: ' + err.message); } }

4-2. 日付フォーマットの統一

HTML5のdate inputは「YYYY-MM-DD」形式ですが、Firestoreには「YYYYMMDD」形式で保存する必要があったため、変換処理を実装しました。

const formatDate = (dateString: string) => { return dateString.replace(/-/g, ''); // "2026-02-14" → "20260214" }; const formatDisplayDate = (dateStr: string) => { const year = dateStr.substring(0, 4); const month = dateStr.substring(4, 6); const day = dateStr.substring(6, 8); return `${year}/${month}/${day}`; // "20260214" → "2026/02/14" };

4-3. 過去の予約を除外する処理

予約一覧では、過去の予約を表示しないようにフィルタリングが必要です。

const today = new Date(); today.setHours(0, 0, 0, 0); const reserveList: Reservation[] = []; querySnapshot.forEach((doc) => { const data = doc.data(); const reserveDate = new Date( parseInt(data.date.substring(0, 4)), parseInt(data.date.substring(4, 6)) - 1, parseInt(data.date.substring(6, 8)) ); // 未来の予約のみリストに追加 if (reserveDate >= today) { reserveList.push({...data}); } });

⚠️ よくある間違い

月は0始まり(0=1月、11=12月)なので、`parseInt(data.date.substring(4, 6)) – 1`とする必要があります。これを忘れると日付が1ヶ月ずれます。

5. パフォーマンス最適化

5-1. useEffectの依存配列

不要な再レンダリングを防ぐため、useEffectの依存配列を適切に設定しました。

useEffect(() => { const fetchGroup = async () => { if (auth.currentUser) { const docRef = doc(db, 'group', auth.currentUser.uid); const docSnap = await getDoc(docRef); if (docSnap.exists()) { setGroup({ uid: auth.currentUser.uid, ...docSnap.data() } as Group); } } }; fetchGroup(); }, []); // 空配列 = 初回レンダリング時のみ実行

5-2. レスポンシブデザイン

モバイルファーストで設計し、メディアクエリを使用してタブレット・PC向けのレイアウトを調整しました。

@media (max-width: 768px) { .container { padding: 10px; } .navigation { flex-direction: column; } .navigation button { width: 100%; } .reservation-table { font-size: 0.9rem; } }

6. まとめ

React + Firebase を使用した施設予約システムの実装について、以下のポイントを解説しました:

  • Firebase Authentication による認証機能の実装
  • Cloud Firestoreのデータ構造設計とセキュリティルール
  • 予約の重複チェックロジック
  • 日付フォーマットの変換処理
  • エラーハンドリングとユーザーフレンドリーなメッセージ表示
  • レスポンシブデザインによるモバイル対応

予約システムに限らず、React + Firebaseを使用したWebアプリケーション開発全般に応用できます。同様のシステム開発や、既存システムの改善についてのご相談は、お問い合わせフォームよりお気軽にご連絡ください。

政治家発言アーカイブ | 自動収集システムで国会・ニュース・SNSを一元管理

キオクシア株価トラッカーアプリを作ってみた|リアルタイム検索