今回やること
前回は、新規登録画面のフォームに入力した内容から
newItemを作りました。
今回は、そのnewItemを
CreateScreenから親のAppへ渡し、
実際に一覧データへ追加します。
そのために、これまで固定データとして使っていた
itemsをStateで管理できるようにします。
CreateScreen
↓ newItemを渡す
App
↓ IDを付ける
itemsを更新する
↓
ListScreenに新しい一覧を渡す
itemsをStateで管理する
まず、src/App.jsxを開きます。
これまでは、initialItemsを
そのままListScreenへ渡していました。
<ListScreen
items={initialItems}
onCreate={() => setScreen("create")}
onEdit={() => setScreen("edit")}
/>
しかし、新しいデータを追加するには、
一覧データ自体を変更できるようにする必要があります。
そこで、Appの中に
items Stateを用意します。
const [items, setItems] = useState(initialItems);
itemsが現在の一覧データを保持するstate変数、
setItemsが一覧データを更新するための更新関数です。
初期値には、前回まで使っていた
initialItemsを指定しています。
ListScreenへ渡す値も、
initialItemsからitemsへ変更します。
<ListScreen
items={items}
onCreate={() => setScreen("create")}
onEdit={() => setScreen("edit")}
/>
この時点では一覧の見た目は変わりません。
ただし、これでsetItemsを使って
一覧データを更新できるようになりました。
newItemをCreateScreenから渡す
次に、src/components/CreateScreen.jsxを開きます。
前回のCreateScreenでは、
フォームの入力内容からnewItemを作るところまで進めました。
今回は、そのnewItemを
親のAppへ渡します。
まず、CreateScreenで
propsとしてonCreateを受け取ります。
export default function CreateScreen({ onCreate, onBack }) {
次に、前回作ったhandleSubmitの最後で
onCreateを呼び出します。
const handleSubmit = (event) => {
event.preventDefault();
const newItem = {
...inputValues,
name: inputValues.name.trim(),
price: Number(inputValues.price) || 0,
};
onCreate(newItem);
};
ここで使っているonCreateは、
Appからpropsで渡す関数です。
CreateScreenでonCreateを呼び出し、
引数としてnewItemを渡すことで、
親のAppからpropsで渡された関数を
CreateScreenで呼び出し、作ったデータをAppへ渡します。
AppでnewItemを受け取る
もう一度、src/App.jsxを開きます。
次に、App側で
CreateScreenへ渡す関数を作ります。
関数名はhandleCreateとします。
const handleCreate = (newItem) => {
console.log("Appコンポーネントです");
console.log(newItem);
};
そして、新規登録画面を表示する部分で、
handleCreateを
onCreateというpropsとして渡します。
if (screen === "create") {
return (
<CreateScreen
onCreate={handleCreate}
onBack={() => setScreen("list")}
/>
);
}
これで、次の流れができました。
CreateScreen
onCreate(newItem)
↓
App
handleCreate(newItem)
フォームに値を入力して「登録する」を押し、
Consoleに「Appコンポーネントです」とnewItemが表示されれば、
CreateScreenからAppへ
データを渡せています。
確認できたら、console.log()はこのあと一覧追加の処理へ置き換えます。
追加するデータにIDを付ける
引き続き、src/App.jsxを編集します。
先ほど作ったhandleCreateの
console.log()を削除し、
受け取ったnewItemをコピーして、
idを追加したitemWithIdを作る処理へ置き換えます。
これまでのサンプルデータには、
sample-1のようにあらかじめIDを付けていました。
今回は新しいデータを登録するため、IDも新しく作る必要があります。
今回は、crypto.randomUUID()を使ってIDを作ります。
const handleCreate = (newItem) => {
// 学習用アプリのため、ブラウザ側でIDを生成
const itemWithId = {
...newItem,
id: crypto.randomUUID(),
};
};
crypto.randomUUID()を実行すると、
UUIDと呼ばれるランダムな文字列が作られます。
今回は、それぞれのデータを区別するIDとして使います。
newItemをコピーして、IDを追加した
itemWithIdができました。
itemsへ新しいデータを追加する
引き続き、src/App.jsxを編集します。
ID付きのデータを作れたので、setItemsを使って一覧データを更新します。
setItems((prevItems) => [
...prevItems,
itemWithId,
]);
prevItemsには、
更新前のitemsが入ります。
...prevItemsで現在の一覧データをコピーし、最後にitemWithIdを追加した新しい配列を作っています。
最後に、handleCreateの末尾へ
setScreen("list")を追加して、一覧画面へ戻します。
const handleCreate = (newItem) => {
// 学習用アプリのため、ブラウザ側でIDを生成
const itemWithId = {
...newItem,
id: crypto.randomUUID(),
};
setItems((prevItems) => [
...prevItems,
itemWithId,
]);
setScreen("list");
};
これで、新規登録画面で入力したデータを
一覧へ追加できるようになりました。
ブラウザで確認する
ファイルを保存して、
実際に新しいデータを登録してみましょう。
「新規登録」を押してフォームを入力し、
「登録する」を押します。
一覧画面へ戻り、
入力したデータが一覧の最後に追加されていればOKです。
ReactとJavaScriptを分けて見てみよう
今回登場した内容を、
ReactとJavaScriptに分けて整理します。
今回登場したReactとJavaScriptの要素
| 種類 |
今回登場したもの |
| React |
State、更新関数、props、親からpropsで渡された関数を子コンポーネントが呼び出して親へデータを知らせる処理 |
| JavaScript |
crypto.randomUUID()、スプレッド構文、配列・オブジェクト |
一覧データをStateとして管理し、
更新関数で新しい配列へ置き換える部分はReactでよく使う考え方です。
一方、IDを作ったり、
配列やオブジェクトをコピーしたりする部分はJavaScriptです。
今回のまとめ
itemsをStateで管理できるようにした
CreateScreenからonCreateでnewItemを渡した
AppのhandleCreateでnewItemを受け取った
crypto.randomUUID()で新しいIDを作った
newItemをコピーして、IDを追加したitemWithIdをitemsへ追加した
- Stateの配列を直接変更せず、新しい配列を作って更新した
- 登録後に一覧画面へ戻した
今回の完成コード
今回変更した
App.jsxとCreateScreen.jsxの完成コードです。
src/App.jsx
import { useState } from "react";
import CreateScreen from "./components/CreateScreen";
import EditScreen from "./components/EditScreen";
import ListScreen from "./components/ListScreen";
import { initialItems } from "./initialItems";
export default function App() {
const [items, setItems] = useState(initialItems);
const [screen, setScreen] = useState("list");
const handleCreate = (newItem) => {
// 学習用アプリのため、ブラウザ側でIDを生成
const itemWithId = {
...newItem,
id: crypto.randomUUID(),
};
setItems((prevItems) => [
...prevItems,
itemWithId,
]);
setScreen("list");
};
if (screen === "create") {
return (
<CreateScreen
onCreate={handleCreate}
onBack={() => setScreen("list")}
/>
);
}
if (screen === "edit") {
return <EditScreen onBack={() => setScreen("list")} />;
}
return (
<ListScreen
items={items}
onCreate={() => setScreen("create")}
onEdit={() => setScreen("edit")}
/>
);
}
src/components/CreateScreen.jsx
import { useState } from "react";
import { categories, statuses } from "../options";
const initialInputValues = {
name: "",
category: "本",
status: "気になる",
price: "",
memo: "",
};
export default function CreateScreen({ onCreate, 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,
};
onCreate(newItem);
};
return (
<main className="form-screen">
<header className="screen-header">
<h1>気になるものリスト</h1>
<button
type="button"
className="secondary"
onClick={onBack}
>
一覧へ
</button>
</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で保存して完成