新規登録画面にフォームを作ろう

← 第7
Reactアプリ作成(JS編)8 / 15
9回 →

今回やること

前回は、コンポーネントや初期データを 役割ごとのファイルへ整理しました。

今回は、CreateScreenに 名前・カテゴリ・ステータス・価格・メモを入力するフォームを作ります。

入力途中の値はStateで管理し、 入力内容が変わったときにStateも更新できるようにします。

最後に、入力内容を newItemという新しいデータにまとめ、 コンソールで確認します。

スポンサーリンク

前回用意した選択肢を読み込む

前回、カテゴリとステータスの選択肢を options.jsへまとめました。

今回は、新規登録画面からその選択肢を使います。

CreateScreen.jsxの先頭に、 次のimport文を追加しましょう。

import { categories, statuses } from "../options";

CreateScreen.jsxcomponentsフォルダの中にあり、 options.jsはその1つ上のsrcにあります。

そのため、1つ上のフォルダを表す ../を使って読み込んでいます。

フォームの入力値をStateで管理する

今回はCreateScreenの中でStateを使うため、 まずuseStateを読み込みます。

CreateScreen.jsxの先頭に追加します。

import { useState } from "react";

次に、フォームに最初から入れておく値を用意します。

CreateScreen.jsxのimport文の下、 CreateScreenコンポーネントの外側に initialInputValuesを追加してください。

const initialInputValues = {
  name: "",
  category: "本",
  status: "気になる",
  price: "",
  memo: "",
};

名前・価格・メモは空の状態から始め、 カテゴリとステータスには最初から表示する値を入れています。

続いて、CreateScreenコンポーネントの中に Stateを追加します。

const [inputValues, setInputValues] = useState(initialInputValues);

inputValuesが現在の入力値を保持するstate変数、 setInputValuesがStateを更新するための更新関数です。

Stateの値には、名前・カテゴリ・ステータス・価格・メモを持つ 1つのオブジェクトを使っています。

まずは名前の入力欄を作る

いきなり5つすべての入力欄を作るのではなく、 まずは名前だけを作ってみましょう。

CreateScreenh2の下に formを追加し、 その中に名前の入力欄を作ります。

<form>
  <label>
    名前
    <input value={inputValues.name} />
  </label>
</form>

value={inputValues.name}によって、 inputに表示する値をstate変数 inputValuesnameと結び付けています。

ただし、この状態でブラウザを開いて文字を入力しようとしても、 入力内容は変わりません。

inputに表示される内容が inputValues.nameによって決まっているためです。

文字を入力したときに、 State側の値も更新する必要があります。

onChangeで入力値の変更を受け取る

inputの入力内容が変わったときに処理を実行するには、 onChangeを使います。

まずは、名前の入力欄だけを更新できるようにしてみましょう。

<form>
  <label>
    名前
    <input
      value={inputValues.name}
      onChange={(event) => {
        setInputValues({
          ...inputValues,
          name: event.target.value,
        });
      }}
    />
  </label>
</form>

少し長いコードなので、順番に見ていきます。

onChange

onChange={...}

onChangeは、 入力内容が変わったときに処理を実行するために使います。

event

(event) => {
  ...
}

入力内容が変わると、 そのときの情報がeventとして渡されます。

event.target.value

event.target.value

event.targetは、 今回変更されたinputを表します。

そのvalueを見ることで、 現在入力されている文字を取得できます。

setInputValuesでStateを更新する

setInputValues({
  ...inputValues,
  name: event.target.value,
});

setInputValuesを使って、 Stateを更新しています。

...inputValuesで現在の入力値を残し、 nameだけを新しい入力値へ変更しています。

文字を入力する
↓
onChangeが実行される
↓
event.target.valueで入力値を取得する
↓
setInputValuesでnameを更新する
↓
新しい値がinputに表示される

ファイルを保存してブラウザを確認し、 名前の入力欄へ文字を入力できればOKです。

新規登録画面で名前を入力
新規登録画面で名前を入力しています。

handleChangeにまとめる

名前の入力欄は動くようになりました。

ただし、このままカテゴリ・ステータス・価格・メモにも 同じようなonChangeを書くと、 似たコードを何度も書くことになります。

そこで、入力値を更新する処理を handleChangeという関数にまとめます。

CreateScreenコンポーネントの中で、 Stateの下に追加してください。

const handleChange = (event) => {
  const fieldName = event.target.name;
  const value = event.target.value;

  const newInputValues = { ...inputValues };
  newInputValues[fieldName] = value;

  setInputValues(newInputValues);
};

少し長くなったので、上から順番に見ていきましょう。

どの入力欄が変更されたか取得する

const fieldName = event.target.name;

event.targetは、 今回変更された入力欄を表します。

どの入力欄が変更されたのか分かるように、 inputにはname属性を追加します。

<input
  name="name"
  value={inputValues.name}
  onChange={handleChange}
/>

名前の入力欄ではname="name"としているため、 event.target.nameから"name"を取得できます。

取得した値を、fieldNameという変数に入れています。

入力された値を取得する

const value = event.target.value;

event.target.valueから、 現在入力されている値を取得します。

ここまでで、 「どの入力欄が変更されたか」と「何が入力されたか」 の2つが分かりました。

たとえば、名前の入力欄に「React入門本」と入力した場合は、 次のようになります。

fieldName → "name"
value     → "React入門本"

現在の入力値をコピーする

const newInputValues = { ...inputValues };

次に、現在のinputValuesをコピーして、 newInputValuesという新しいオブジェクトを作ります。

...inputValuesは、 JavaScriptのスプレッド構文です。

変更された項目を書き換える

newInputValues[fieldName] = value;

fieldNameには、 変更された入力欄のname属性の値が入っています。

この値をnewInputValuesプロパティ名として使い、 変更する項目を指定します。

たとえば、名前の入力欄にはname="name"を付けているため、 fieldNameには"name"が入ります。

newInputValues["name"] = value;

これで、newInputValuesnameプロパティだけを新しい入力値に変更できます。

カテゴリの入力欄でname="category"とすれば、 同じ処理でcategoryを変更できます。

新しい入力値でStateを更新する

setInputValues(newInputValues);

最後に、更新関数setInputValuesnewInputValuesを渡してStateを更新します。

入力内容が変わる
↓
どの入力欄かを取得する
↓
入力された値を取得する
↓
現在のinputValuesをコピーする
↓
変更された項目を書き換える
↓
新しいオブジェクトでStateを更新する

このhandleChangeを使えば、 名前だけでなく、ほかの入力欄も同じ処理で更新できます。

カテゴリとステータスを追加する

名前の入力欄を作れたので、 次はカテゴリとステータスのselectを追加します。

まずは、先ほど作った名前の入力欄の下に、 カテゴリを追加してください。

カテゴリの選択肢には、 前回用意したcategories配列を使います。

<label>
  カテゴリ
  <select
    name="category"
    value={inputValues.category}
    onChange={handleChange}
  >
    {categories.map((value) => (
      <option key={value} value={value}>
        {value}
      </option>
    ))}
  </select>
</label>

categories配列をmap()で処理し、 1つずつoptionとして表示しています。

map()keyの考え方は、 一覧表示のときと同じです。

カテゴリの下には、 ステータスを続けて追加します。

<label>
  ステータス
  <select
    name="status"
    value={inputValues.status}
    onChange={handleChange}
  >
    {statuses.map((value) => (
      <option key={value} value={value}>
        {value}
      </option>
    ))}
  </select>
</label>

name="category"name="status"を付けているため、 どちらも同じhandleChangeで更新できます。

価格とメモを追加する

残りの価格とメモは、 ステータスの入力欄の下に続けて追加します。

<label>
  価格
  <input
    name="price"
    type="number"
    min="0"
    value={inputValues.price}
    onChange={handleChange}
  />
</label>

<label>
  メモ
  <textarea
    name="memo"
    value={inputValues.memo}
    onChange={handleChange}
  />
</label>

価格にはtype="number"、 メモには複数行入力できるtextareaを使います。

これで、5つすべての入力項目がそろいました。

ファイルを保存して、 名前・カテゴリ・ステータス・価格・メモを それぞれ入力・変更できることを確認してみましょう。

名前、カテゴリ、ステータス、価格、メモを入力できる新規登録フォーム
新規登録画面に5つの入力項目が表示されています。

登録ボタンを追加する

入力欄がそろったので、 formの最後に「登録する」ボタンを追加します。

<button type="submit">
  登録する
</button>

type="submit"を指定すると、 このボタンはフォームを送信するためのボタンになります。

フォームの送信処理を作る

「登録する」を押したときの処理を作りましょう。

フォームが送信されたときの処理は、 onSubmitで受け取ります。

先ほどから使っているformに、 onSubmitを追加します。

<form onSubmit={handleSubmit}>

次に、CreateScreenコンポーネントの中へ handleSubmitを追加します。

const handleSubmit = (event) => {
  event.preventDefault();

  console.log(inputValues);
};

HTMLのformには、 本来のフォーム送信処理があります。

フォームを通常どおり送信すると、 ブラウザは指定された送信先へデータを送り、 ページの移動や再読み込みを行います。

今回は、その標準の送信処理ではなく、 React側のJavaScriptで入力内容を処理したいので、 event.preventDefault()を使います。

preventDefault()は、 イベントが本来行う標準の動作を止めるためのメソッドです。

「登録する」を押す
↓
formのonSubmitが実行される
↓
handleSubmitが呼ばれる
↓
preventDefault()で通常のフォーム送信を止める
↓
JavaScriptで入力内容を処理する

名前や価格、メモなどを入力して「登録する」を押し、 ブラウザの開発者ツールでConsoleタブを確認してみましょう。

入力した内容を持つオブジェクトが表示されれば、フォームの値をStateで正しく管理し、送信時に取得できています。

開発者ツールのConsoleに表示される入力データ
Consoleに入力データが表示されています。

このconsole.log(inputValues)は、 次のセクションでnewItemを確認するコードへ変更します。

登録するデータを作る

入力内容を取得できたので、 登録する1件分のデータを作りましょう。

今回は、名前を必須にし、名前の前後にある空白を取り除きます。
また、inputから取得した価格は文字列として扱われるため、 数値へ変換してから登録します。

名前の入力を必須にする

名前のinputにrequiredを追加してください。

<input
  name="name"
  value={inputValues.name}
  onChange={handleChange}
  required
/>

requiredを付けると、 名前が未入力のままフォームを送信しようとしたときに、 ブラウザが入力を求めてくれます。

入力内容をnewItemにまとめる

登録するデータは、 newItemという新しいオブジェクトにまとめます。

const newItem = {
  ...inputValues,
  name: inputValues.name.trim(),
  price: Number(inputValues.price) || 0,
};
  • ...inputValues:現在の入力内容をコピーする
  • trim():名前の前後にある空白を取り除く
  • Number():価格を数値へ変換する

価格が未入力の場合は、今回は0として扱います。

作ったデータをコンソールで確認する

handleSubmitを次のように変更します。

const handleSubmit = (event) => {
  event.preventDefault();

  const newItem = {
    ...inputValues,
    name: inputValues.name.trim(),
    price: Number(inputValues.price) || 0,
  };

  console.log(newItem);
};

フォームに値を入力して「登録する」を押し、 コンソールを確認してください。

入力した内容を持つnewItemが表示されればOKです。

これで、新規登録画面で入力した内容を 登録する1件分のデータとしてまとめられるようになりました。

ReactとJavaScriptを分けて見てみよう

今回登場した内容を、 ReactとJavaScriptに分けて整理します。

今回登場したReactとJavaScriptの要素
種類 今回登場したもの
React State、state変数、更新関数、valueonChangeonSubmit
JavaScript オブジェクト、スプレッド構文、trim()Number()preventDefault()

フォームの入力値をStateと結び付けたり、 onChangeonSubmitで処理したりする部分は、 Reactでよく使う書き方です。

一方、オブジェクトをコピーしたり、 文字列や数値を整えたりする部分はJavaScriptです。

今回のまとめ

  • フォームの入力値を1つのStateで管理した
  • inputValuesをstate変数、setInputValuesを更新関数として使った
  • valueで入力欄とStateの値を結び付けた
  • onChangeで入力内容の変更を受け取った
  • event.target.nameで変更された入力欄を取得した
  • event.target.valueで現在の入力値を取得した
  • Stateのオブジェクトをコピーしてから変更した
  • handleChangeで複数の入力欄の更新処理を共通化した
  • categoriesstatusesから選択肢を表示した
  • onSubmitでフォーム送信を受け取った
  • event.preventDefault()でフォームの標準送信を止めた
  • 入力内容からnewItemを作った

今回の完成コード

今回変更したCreateScreen.jsxの完成コードです。

src/components/CreateScreen.jsx

import { useState } from "react";

import { categories, statuses } from "../options";

const initialInputValues = {
  name: "",
  category: "本",
  status: "気になる",
  price: "",
  memo: "",
};

export default function CreateScreen({ onBack }) {
  const [inputValues, setInputValues] = useState(initialInputValues);

  const handleChange = (event) => {
    const fieldName = event.target.name;
    const value = event.target.value;

    const newInputValues = { ...inputValues };
    newInputValues[fieldName] = value;

    setInputValues(newInputValues);
  };

  const handleSubmit = (event) => {
    event.preventDefault();

    const newItem = {
      ...inputValues,
      name: inputValues.name.trim(),
      price: Number(inputValues.price) || 0,
    };

    console.log(newItem);
  };

  return (
    <main className="form-screen">
      <header className="screen-header">
        <h1>気になるものリスト</h1>
        <div className="header-actions">
          <button type="button" className="secondary" onClick={onBack}>
            一覧へ
          </button>
        </div>
      </header>

      <h2 className="screen-title">新規登録画面</h2>

      <form onSubmit={handleSubmit}>
        <label>
          名前
          <input
            name="name"
            value={inputValues.name}
            onChange={handleChange}
            required
          />
        </label>

        <label>
          カテゴリ
          <select
            name="category"
            value={inputValues.category}
            onChange={handleChange}
          >
            {categories.map((value) => (
              <option key={value} value={value}>
                {value}
              </option>
            ))}
          </select>
        </label>

        <label>
          ステータス
          <select
            name="status"
            value={inputValues.status}
            onChange={handleChange}
          >
            {statuses.map((value) => (
              <option key={value} value={value}>
                {value}
              </option>
            ))}
          </select>
        </label>

        <label>
          価格
          <input
            name="price"
            type="number"
            min="0"
            value={inputValues.price}
            onChange={handleChange}
          />
        </label>

        <label>
          メモ
          <textarea
            name="memo"
            value={inputValues.memo}
            onChange={handleChange}
          />
        </label>

        <button type="submit">
          登録する
        </button>
      </form>
    </main>
  );
}

アプリの完成状況

  • ✅ Reactを動かし、JSXを表示する
  • ✅ Stateで画面を切り替える
  • ✅ 1件のデータを表示する
  • ✅ 複数のデータを一覧表示する
  • ✅ コンポーネントとpropsを使う
  • ✅ コンポーネントを別ファイルへ分ける
  • ✅ 新規登録画面を作る
  • ⬜ 一覧データをStateで管理する
  • ⬜ 編集画面を作る
  • ⬜ データを更新する
  • ⬜ データを削除する
  • ⬜ キーワードで検索する
  • ⬜ カテゴリ・ステータスで絞り込む
  • ⬜ localStorageで保存して完成
スポンサーリンク