6-4. フォームの型を総合的に定義する

複数の入力を持つフォームの型を定義する方法を学びます

React - 第4章: TypeScript + React

課題:フォームの型を総合的に定義しよう

これまで学んだことを活かして、複数の入力を持つフォームに型を定義します。 ユーザー登録フォームを作って、型安全にデータを扱ってみましょう。

やること

  1. 1.UserForm インターフェースを定義する
  2. 2.プロパティ:username(string)、email(string)、age(number)
  3. 3.フォームの状態を UserForm 型で管理する
  4. 4.イベントハンドラに正しい型を指定する
  5. 5.送信すると、入力内容を表示する

フォーム全体の型定義

フォームデータの型を定義します:

interface UserForm {
  username: string;
  email: string;
  age: number;
}

状態と提出済みデータの型を指定します:

const [formData, setFormData] = useState<UserForm>({
  username: "",
  email: "",
  age: 0
});

const [submitted, setSubmitted] = useState<UserForm | null>(null);

入力を更新する関数:

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
  const { name, value } = e.target;
  setFormData({
    ...formData,
    [name]: name === 'age' ? Number(value) : value
  });
};

ポイント:フォーム全体の型を1つのインターフェースにまとめると、管理しやすくなります。

期待される出力

ユーザー登録フォーム
[ユーザー名: 山田太郎]
[メール: [email protected]]
[年齢: 25]
[登録]

登録情報:
ユーザー名: 山田太郎
メール: [email protected]
年齢: 25歳
import { useState, ChangeEvent, FormEvent } from 'react';

// ここにUserFormインターフェースを定義してください
// username: string
// email: string
// age: number


function App() {
  // ここのuseStateにUserForm型を指定してください
  const [formData, setFormData] = useState({
    username: "",
    email: "",
    age: 0
  });
  
  // ここのuseStateにUserForm | null型を指定してください
  const [submitted, setSubmitted] = useState(null);
  
  const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
    const { name, value } = e.target;
    setFormData({
      ...formData,
      [name]: name === 'age' ? Number(value) : value
    });
  };
  
  const handleSubmit = (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    setSubmitted(formData);
  };
  
  return (
    <div style={{ padding: '20px', maxWidth: '500px', margin: '0 auto' }}>
      <h2>ユーザー登録フォーム</h2>
      
      <form onSubmit={handleSubmit} style={{ marginTop: '20px' }}>
        <div style={{ marginBottom: '15px' }}>
          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
            ユーザー名
          </label>
          <input 
            type="text"
            name="username"
            value={formData.username}
            onChange={handleChange}
            placeholder="山田太郎"
            required
            style={{ 
              width: '100%',
              padding: '10px',
              fontSize: '16px',
              border: '1px solid #ccc',
              borderRadius: '4px'
            }}
          />
        </div>
        
        <div style={{ marginBottom: '15px' }}>
          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
            メールアドレス
          </label>
          <input 
            type="email"
            name="email"
            value={formData.email}
            onChange={handleChange}
            placeholder="[email protected]"
            required
            style={{ 
              width: '100%',
              padding: '10px',
              fontSize: '16px',
              border: '1px solid #ccc',
              borderRadius: '4px'
            }}
          />
        </div>
        
        <div style={{ marginBottom: '15px' }}>
          <label style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>
            年齢
          </label>
          <input 
            type="number"
            name="age"
            value={formData.age || ""}
            onChange={handleChange}
            placeholder="25"
            required
            min="0"
            style={{ 
              width: '100%',
              padding: '10px',
              fontSize: '16px',
              border: '1px solid #ccc',
              borderRadius: '4px'
            }}
          />
        </div>
        
        <button 
          type="submit"
          style={{ 
            width: '100%',
            padding: '12px',
            fontSize: '16px',
            backgroundColor: '#007bff',
            color: 'white',
            border: 'none',
            borderRadius: '4px',
            cursor: 'pointer',
            fontWeight: 'bold'
          }}
        >
          登録
        </button>
      </form>
      
      {submitted && (
        <div style={{ 
          marginTop: '30px',
          padding: '20px',
          backgroundColor: '#d4edda',
          border: '2px solid #c3e6cb',
          borderRadius: '8px'
        }}>
          <h3 style={{ marginTop: 0, color: '#155724' }}>登録情報</h3>
          <p><strong>ユーザー名:</strong> {submitted.username}</p>
          <p><strong>メール:</strong> {submitted.email}</p>
          <p><strong>年齢:</strong> {submitted.age}</p>
        </div>
      )}
    </div>
  );
}

export default App;