質問への追記・修正依頼欄より
実は今回質問させていただいたのはとある機能を実装するための一部分でして最終的には以下のような処理にしたいです。
1.「追加する」を押すと、inputタグが2つ生成される
(1つ目は金額を入力するinputタグ、2つ目は人数を入力するinputタグ)
2.金額のinputタグに「1000」、人数のinputタグに「3」を入力すると、空の配列に「1000」を3つ格納する
例えば、
空の配列array1があるとします。
let array1 = [];
次に「追加する」を1回押したとします。
すると、金額を入力するinputタグと人数を入力するinputタグの2つを生成します。
次に金額inputに「1500」、人数inputタグに「2」を入力したとすると、array1の中身は
let array1 = [1500, 1500];
となります。
続いて、もう一度、「追加する」ボタンを押したとします。
すると、別の金額を入力するinputタグと人数を入力するinputタグが生成されます。
そして、新しく生成された金額inputに「2500」、人数inputに「3」を入力すると、array1の中身は
let array1 = [1500, 1500, 2500, 2500, 2500]
になります。
以上のような機能を実装するにはどのようなコードが正しいのでしょうか?
こんな感じでしょうか。
jsx
1import { useState } from 'react';
2
3export const Game = () => {
4 const [items, setItems] = useState([]);
5
6 const createInput = () => {
7 setItems([...items, { amount: '', people: '' }])
8 };
9
10 const updateAmount = (index, value) => {
11 const newItems = [...items];
12 newItems[index] = { ...items[index], amount: value };
13 setItems(newItems)
14 };
15
16 const updatePeople = (index, value) => {
17 const newItems = [...items];
18 newItems[index] = { ...items[index], people: value };
19 setItems(newItems)
20 };
21
22 const array1 = [];
23 items.forEach(item => {
24 const amount = parseInt(item.amount) || 0;
25 const people = parseInt(item.people) || 0;
26 for (let i = 0; i < people; i++) {
27 array1.push(amount);
28 }
29 });
30
31 return (
32 <div className="game">
33 <button type="button" onClick={createInput}>
34 追加する
35 </button>
36 {items.map((item, i) => (
37 <div key={i}>
38 金額: <input type="number" value={item.amount} onChange={e => updateAmount(i, e.target.value)} /> 円、
39 人数: <input type="number" value={item.people} onChange={e => updatePeople(i, e.target.value)} /> 人
40 </div>
41 ))}
42 <p>
43 array1 = {JSON.stringify(array1)}
44 </p>
45 </div>
46 );
47};
ポイントは、
- React では入力欄の数だけ状態 (state) が必要です。入力欄の数が可変の場合は配列などを使いましょう。
- 1 行の内容 (金額と人数) を一つのオブジェクト { amount: 金額, people: 人数 } として、その配列を用意します。
- 配列の中身を書き換える場合、配列そのものと、値が変更されるオブジェクトは作り直す必要があります。
- array1 は入力内容に基づいて計算できるので、毎回計算し直すと良いでしょう。
という感じで、普通の JavaScript とはまったく違う考え方をする必要があります。
コメント欄へのお返事
まず、...
の意味はご存じでしょうか? これはスプレッド構文と言って、配列やオブジェクトの中身を取り出して、新しい配列やオブジェクトに埋め込む、という意味になります。
参考: JSのスプレッド構文を理解する - Qiita
例えば配列の場合、
js
1const a = [1, 2, 3];
2const b = [...a, 4];
と書くと、...a
の部分には a の中身が展開されて、b の値は [1, 2, 3, 4]
になります。
配列の中身を書き換える場合、配列そのものと、値が変更されるオブジェクトは作り直す必要があります。
と書きましたが、例えば createInput を
js
1 const createInput = () => {
2 items.push({ amount: '', people: '' });
3 setItems(items)
4 };
と書き換えると、追加ボタンを押しても何も起こらなくなります。これは、items の中身は書き変わったけど、items 自体は同じ配列なので、setItems しても値は変わってないとみなされてしまうためです。
同様に、updateAmount で const newItems = [...items];
としているのは、items 配列をコピーして setItems の時に新しい値として認識させるためです。
次の newItems[index] = { ...items[index], amount: value };
ですが、これはオブジェクトのスプレッド構文になります。例えば、
js
1const x = { a: 1, b: 2, c: 3 };
2const y = { ...x, a: 4 };
とすると、...x
の部分に x の中身が展開されるのは配列の場合と同様ですが、違うのは a というキーが重複していることですね。その場合、後ろに書いたものが優先されます。ので、y の値は { a: 1, b: 2, c: 3, a: 4 }
ではなく、後ろの a: 4
が優先されて { a: 4, b: 2, c: 3 }
になります。
今回の場合、items の各要素には amount と people しかないので、次のように書いても同じ結果になります。
js
1 newItems[index] = { amount: value, people: newItems[index].people };
が、スプレッド構文を使うと amount だけを書き換えたい、ということが明確になりますし、amount と people 以外に情報が増えても大丈夫です。
それから、array1 を計算する部分ですが、
js
1const amount = parseInt(item.amount) || 0;
2const people = parseInt(item.people) || 0;
実は item.amount や item.people は文字列になってます。これは、<input> の value に渡す時には文字列にしておかないと空欄にできないためですが、文字列なので例えば abc とか入力されたら parseInt の結果は NaN という、エラーを表す特別な値になります。これを確実に数値にする (エラーだったら 0 にする) ために || 0
を付けています。
参考: JavaScript parseInt, paeseFloat が NaN になるとき 0 を返したい - かもメモ