今回やること
前回は、コンポーネントや初期データを 役割ごとのファイルへ整理しました。
今回は、CreateScreenに
名前・カテゴリ・ステータス・価格・メモを入力するフォームを作ります。
入力途中の値はStateで管理し、 入力内容が変わったときにStateも更新できるようにします。
最後に、入力内容を
newItemという新しいデータにまとめ、
コンソールで確認します。
公開日:更新日:
前回は、コンポーネントや初期データを 役割ごとのファイルへ整理しました。
今回は、CreateScreenに
名前・カテゴリ・ステータス・価格・メモを入力するフォームを作ります。
入力途中の値はStateで管理し、 入力内容が変わったときにStateも更新できるようにします。
最後に、入力内容を
newItemという新しいデータにまとめ、
コンソールで確認します。
前回、カテゴリとステータスの選択肢を
options.jsへまとめました。
今回は、新規登録画面からその選択肢を使います。
CreateScreen.jsxの先頭に、
次のimport文を追加しましょう。
import { categories, statuses } from "../options";
CreateScreen.jsxは
componentsフォルダの中にあり、
options.jsはその1つ上のsrcにあります。
そのため、1つ上のフォルダを表す
../を使って読み込んでいます。
今回は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つすべての入力欄を作るのではなく、 まずは名前だけを作ってみましょう。
CreateScreenのh2の下に
formを追加し、
その中に名前の入力欄を作ります。
<form>
<label>
名前
<input value={inputValues.name} />
</label>
</form>
value={inputValues.name}によって、
inputに表示する値をstate変数
inputValuesのnameと結び付けています。
ただし、この状態でブラウザを開いて文字を入力しようとしても、 入力内容は変わりません。
inputに表示される内容が
inputValues.nameによって決まっているためです。
文字を入力したときに、 State側の値も更新する必要があります。
inputの入力内容が変わったときに処理を実行するには、
onChangeを使います。
まずは、名前の入力欄だけを更新できるようにしてみましょう。
<form>
<label>
名前
<input
value={inputValues.name}
onChange={(event) => {
setInputValues({
...inputValues,
name: event.target.value,
});
}}
/>
</label>
</form>
少し長いコードなので、順番に見ていきます。
onChange={...}
onChangeは、
入力内容が変わったときに処理を実行するために使います。
(event) => {
...
}
入力内容が変わると、
そのときの情報がeventとして渡されます。
event.target.value
event.targetは、
今回変更されたinputを表します。
そのvalueを見ることで、
現在入力されている文字を取得できます。
setInputValues({
...inputValues,
name: event.target.value,
});
setInputValuesを使って、
Stateを更新しています。
...inputValuesで現在の入力値を残し、
nameだけを新しい入力値へ変更しています。
文字を入力する
↓
onChangeが実行される
↓
event.target.valueで入力値を取得する
↓
setInputValuesでnameを更新する
↓
新しい値がinputに表示される
ファイルを保存してブラウザを確認し、 名前の入力欄へ文字を入力できればOKです。
名前の入力欄は動くようになりました。
ただし、このままカテゴリ・ステータス・価格・メモにも
同じような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;
これで、newInputValuesの
nameプロパティだけを新しい入力値に変更できます。
カテゴリの入力欄でname="category"とすれば、
同じ処理でcategoryを変更できます。
setInputValues(newInputValues);
最後に、更新関数setInputValuesへ
newInputValuesを渡して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つすべての入力項目がそろいました。
ファイルを保存して、 名前・カテゴリ・ステータス・価格・メモを それぞれ入力・変更できることを確認してみましょう。
入力欄がそろったので、
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.log(inputValues)は、
次のセクションでnewItemを確認するコードへ変更します。
入力内容を取得できたので、 登録する1件分のデータを作りましょう。
今回は、名前を必須にし、名前の前後にある空白を取り除きます。
また、inputから取得した価格は文字列として扱われるため、
数値へ変換してから登録します。
名前のinputにrequiredを追加してください。
<input
name="name"
value={inputValues.name}
onChange={handleChange}
required
/>
requiredを付けると、
名前が未入力のままフォームを送信しようとしたときに、
ブラウザが入力を求めてくれます。
登録するデータは、
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 | State、state変数、更新関数、value、onChange、onSubmit |
| JavaScript | オブジェクト、スプレッド構文、trim()、Number()、preventDefault() |
フォームの入力値をStateと結び付けたり、
onChangeやonSubmitで処理したりする部分は、
Reactでよく使う書き方です。
一方、オブジェクトをコピーしたり、 文字列や数値を整えたりする部分はJavaScriptです。
inputValuesをstate変数、setInputValuesを更新関数として使ったvalueで入力欄とStateの値を結び付けたonChangeで入力内容の変更を受け取ったevent.target.nameで変更された入力欄を取得したevent.target.valueで現在の入力値を取得したhandleChangeで複数の入力欄の更新処理を共通化したcategoriesとstatusesから選択肢を表示したonSubmitでフォーム送信を受け取ったevent.preventDefault()でフォームの標準送信を止めたnewItemを作った
今回変更した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>
);
}