今回やること
前回は、新規登録画面のフォームに入力した内容から
newItemを作りました。
今回は、そのnewItemを
CreateScreenから親のAppへ渡し、
実際に一覧データへ追加します。
そのために、これまで固定データとして使っていた
itemsをStateで管理できるようにします。
CreateScreen
↓ newItemを渡す
App
↓ IDを付ける
itemsを更新する
↓
ListScreenに新しい一覧を渡す
今回の完成イメージ:登録したデータが一覧に追加された画面
1. 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を使って
一覧データを更新できるようになりました。
2. CreateScreenからAppへnewItemを渡そう
新規登録画面で作ったnewItemは、
CreateScreenの中にあります。
一覧データを管理しているのはAppなので、
まずはnewItemをAppへ渡します。
今回は少し処理の流れが複雑なので、
まず全体の流れを確認しておきましょう。
①AppでhandleCreateを作る
↓
②handleCreateをCreateScreenに渡す
↓
③CreateScreenでonCreateを受け取る
↓
④onCreateにnewItemを渡す
↓
AppのhandleCreate(newItem)が実行される
① AppでhandleCreateを作る
まず、src/App.jsxを開きます。
CreateScreenから渡される
newItemを受け取る関数として、
handleCreateを作ります。
const handleCreate = (newItem) => {
console.log("Appコンポーネントです");
console.log(newItem);
};
今は、newItemを受け取れたことを確認するために、
Consoleへ表示するだけにしておきます。
② handleCreateをCreateScreenに渡す
次に、新規登録画面を表示する部分で、
handleCreateを
onCreateというpropsとして渡します。
if (screen === "create") {
return (
<CreateScreen
onCreate={handleCreate}
onBack={() => setScreen("list")}
/>
);
}
これで、AppからCreateScreenへ
handleCreateを渡せるようになりました。
③ CreateScreenでonCreateを受け取る
続いて、src/components/CreateScreen.jsxを開きます。
Appから渡された
onCreateをpropsとして受け取ります。
export default function CreateScreen({ onCreate, onBack }) {
④ onCreateにnewItemを渡す
最後に、前回作ったhandleSubmitの末尾で
onCreateを呼び出します。
const handleSubmit = (event) => {
event.preventDefault();
const newItem = {
...inputValues,
name: inputValues.name.trim(),
price: Number(inputValues.price) || 0,
};
onCreate(newItem);
};
onCreate(newItem)を実行すると、
Appから渡されたhandleCreateが呼び出されます。
その引数としてnewItemが渡されるため、
AppのhandleCreate(newItem)で受け取れます。
ConsoleでnewItemを確認しよう
フォームに値を入力して「登録する」を押し、
Consoleを確認しましょう。
「Appコンポーネントです」と
newItemが表示されれば、
CreateScreenからAppへ
データを渡せています。
確認できたら、
console.log()は次の処理へ置き換えます。
3. 追加するデータにIDを付けよう
引き続き、src/App.jsxを編集します。
先ほど作ったhandleCreateの
console.log()を削除し、
受け取ったnewItemにIDを付けます。
これまでのサンプルデータには、
sample-1のようにあらかじめIDを付けていました。
今回は新しいデータを登録するため、
IDも新しく作る必要があります。
今回は、crypto.randomUUID()を使ってIDを作ります。
const handleCreate = (newItem) => {
// 学習用アプリのため、ブラウザ側でIDを生成
const itemWithId = {
...newItem,
id: crypto.randomUUID(),
};
};
crypto.randomUUID()を実行すると、
UUIDと呼ばれるランダムな文字列が作られます。
今回は、それぞれのデータを区別するIDとして使います。
...newItemで入力内容をコピーし、
idを追加した
itemWithIdを作っています。
4. itemsへ新しいデータを追加しよう
ID付きのデータを作れたので、
setItemsを使って一覧データへ追加します。
新しいデータをitemsへ追加する
handleCreateの
itemWithIdを作る処理の下に、
次のコードを追加します。
setItems((prevItems) => [
...prevItems,
itemWithId,
]);
prevItemsには、
更新前のitemsが入ります。
...prevItemsで現在の一覧データをコピーし、
最後にitemWithIdを追加した新しい配列を作っています。
登録後に一覧画面へ戻す
データを追加したら、
setScreen("list")を実行して
一覧画面へ戻します。
handleCreate全体は、次のようになります。
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でよく使う考え方です。
また、propsで渡した関数を呼び出すことで、
子コンポーネントで作ったデータを親コンポーネントへ渡しています。
一方、IDを作ったり、
配列やオブジェクトをコピーしたりする部分はJavaScriptです。
今回のまとめ
itemsをStateで管理できるようにした
CreateScreenからonCreateでnewItemを渡した
AppのhandleCreateでnewItemを受け取った
crypto.randomUUID()で新しいIDを作った
newItemをコピーし、IDを追加したitemWithIdを作った
setItemsで新しいデータを一覧へ追加した
- 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>
<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で保存して完成