今回やること
前回は、新規登録画面で作ったデータを
Appコンポーネントへ渡し、
一覧へ追加できるようにしました。
今回は、一覧の「編集」ボタンをクリックしたときに、
どのデータを編集するのかを覚えておき、
そのデータを編集画面のフォームへ表示します。
編集画面では、新規登録画面と同じように
入力途中の値をStateで管理します。
一覧の「編集」をクリック
↓
編集するデータのIDをAppコンポーネントで覚える
↓
itemsから対象のデータを探す
↓
EditScreenコンポーネントへ渡す
↓
フォームに現在の値を表示する
今回は、編集するデータをフォームへ表示するところまで進めます。
実際にデータを更新する処理は次回作ります。
今回の完成イメージ:選択したデータが入った編集画面
1. 編集するデータのIDを覚えよう
一覧には複数のデータがあるため、
編集画面へ移動するときに
どのデータを編集するのかを覚えておく必要があります。
今回は、編集するデータのidを
Stateとして管理します。
src/App.jsxを開き、
screen Stateの下に次のStateを追加してください。
const [editingId, setEditingId] = useState(null);
editingIdが現在編集するデータのIDを保持するstate変数、
setEditingIdがその値を更新するための更新関数です。
最初は編集するデータが決まっていないため、
初期値にはnullを指定しています。
2. 編集するデータをitemsから探そう
次に、editingIdと一致するデータを
itemsから探します。
引き続きsrc/App.jsxで、
Stateの下に次のコードを追加してください。
const editingItem =
items.find((item) => item.id === editingId) ?? null;
find()は、
条件に一致する最初の要素を配列から探す
JavaScriptのメソッドです。
今回は、item.idとeditingIdが
一致するデータを探しています。
たとえばeditingIdが
"sample-2"なら、
idが"sample-2"のデータが
editingItemに入ります。
条件に一致するデータが見つからない場合、
find()はundefinedを返します。
今回は?? nullを付けて、
見つからなかった場合はnullにしています。
まだeditingIdにはnullが入っているため、
この時点のeditingItemもnullです。
次に、一覧から編集するデータを選べるようにします。
3. 一覧から編集するデータを選べるようにしよう
一覧の各データに「編集」ボタンを付け、
クリックしたデータのIDを
Appへ渡せるようにします。
編集ボタンを一覧の行へ移す
src/components/ListScreen.jsxを開きます。
これまでは動作確認のため、
「編集」ボタンを一覧画面のheaderに置いていました。
今回は編集するデータを選べるように、
headerから「編集」ボタンを削除します。
<header className="list-header">
<h1>気になるものリスト</h1>
<div className="header-actions">
<button type="button" onClick={onCreate}>
新規登録
</button>
</div>
</header>
次に、テーブルの見出しへ
「操作」列を追加します。
<thead>
<tr>
<th>名前</th>
<th>カテゴリ</th>
<th>ステータス</th>
<th>価格</th>
<th>メモ</th>
<th>操作</th>
</tr>
</thead>
一覧を表示しているmap()の中では、
各行の最後に「編集」ボタンを追加します。
{items.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.category}</td>
<td>{item.status}</td>
<td>{item.price.toLocaleString()}円</td>
<td>{item.memo}</td>
<td>
<button
type="button"
onClick={() => onEdit(item.id)}
>
編集
</button>
</td>
</tr>
))}
「編集」をクリックすると、
onEdit(item.id)が実行されます。
これで、クリックしたデータのIDを
onEditへ渡せます。
AppでIDを受け取る
続いて、src/App.jsxを開きます。
ListScreenから渡されたIDを受け取る
startEditを追加します。
const startEdit = (id) => {
setEditingId(id);
setScreen("edit");
};
setEditingId(id)で編集するデータのIDを覚え、
setScreen("edit")で編集画面へ切り替えています。
ListScreenへ渡している
onEditも、startEditへ変更します。
<ListScreen
items={items}
onCreate={() => setScreen("create")}
onEdit={startEdit}
/>
find()で取得したデータを確認しよう
ここで、クリックしたデータを
find()で正しく取得できているか確認してみましょう。
src/App.jsxで、
先ほど追加したeditingItemのコードのすぐ下に、一時的に次のコードを追加します。
console.log(editingItem);
ファイルを保存して一覧画面を開き、
どれか1件の「編集」ボタンを押してください。
Consoleに選んだデータが表示されれば、
editingIdを使って
itemsから正しいデータを取得できています。
選択したデータがConsoleに表示されます。
確認できたら、
console.log(editingItem)は削除しておきましょう。
4. 編集するデータをEditScreenへ渡そう
Appでは、
editingItemとして
編集する1件分のデータを取得できるようになりました。
次は、そのデータを
EditScreenへpropsで渡します。
src/App.jsxの編集画面を表示している部分を、
次のように変更してください。
if (screen === "edit" && editingItem) {
return (
<EditScreen
item={editingItem}
onBack={() => setScreen("list")}
/>
);
}
item={editingItem}によって、
編集するデータをEditScreenへ渡しています。
また、
screen === "edit" && editingItemとしているため、
編集画面で、なおかつ編集するデータを取得できている場合にだけ
EditScreenを表示します。
5. 編集フォームに現在の値を表示しよう
最後に、受け取ったitemを使って
編集フォームを作ります。
現在の値をStateの初期値にする
src/components/EditScreen.jsxを開きます。
まず、useStateと、
カテゴリ・ステータスの選択肢を読み込みます。
import { useState } from "react";
import { categories, statuses } from "../options";
EditScreenでは、
propsで受け取ったitemの値を使って、
編集フォームのStateを用意します。
export default function EditScreen({ item, onBack }) {
const [inputValues, setInputValues] = useState({
name: item.name,
category: item.category,
status: item.status,
price: String(item.price),
memo: item.memo,
});
// このあと編集フォームを作ります
}
新規登録画面では空の入力値から始めましたが、
編集画面では現在のデータを初期値として使います。
価格はinputで扱うため、
String()で文字列へ変換しておきます。
handleChangeを追加する
続けて、EditScreenコンポーネントの中で、
先ほど追加したinputValues Stateの下に
handleChangeを追加します。
const handleChange = (event) => {
const fieldName = event.target.name;
const value = event.target.value;
const newInputValues = { ...inputValues };
newInputValues[fieldName] = value;
setInputValues(newInputValues);
};
name属性で変更された項目を判断し、
その項目だけを書き換える考え方は、
第8回の新規登録フォームと同じです。
編集フォームを表示する
inputValuesを使って、
名前・カテゴリ・ステータス・価格・メモを表示します。
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 edit-title">編集画面</h2>
<form>
<label>
名前
<input
name="name"
value={inputValues.name}
onChange={handleChange}
/>
</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>
</form>
</main>
);
選んだデータがフォームに表示されることを確認しよう
ファイルを保存して一覧画面へ戻り、
それぞれのデータにある「編集」ボタンを押してみましょう。
選んだデータの名前・カテゴリ・ステータス・価格・メモが
編集フォームに表示され、
入力内容を変更できればOKです。
選択したデータの現在の値が編集フォームに表示されています。
この時点では、入力欄の内容を変更しても
一覧データそのものはまだ更新されません。
実際に更新する処理は次回追加します。
ReactとJavaScriptを分けて見てみよう
今回登場した内容を、
ReactとJavaScriptに分けて整理します。
今回登場したReactとJavaScriptの要素
| 種類 |
今回登場したもの |
| React |
State、props、画面切り替え、フォームの入力値の管理 |
| JavaScript |
find()、String()、オブジェクト、スプレッド構文 |
今回のまとめ
- 編集するデータのIDを
editingId Stateで管理した
find()でitemsから編集するデータを取得した
- 一覧の各行に「編集」ボタンを追加した
- 「編集」ボタンから選んだデータのIDを
Appへ渡した
- 編集するデータを
EditScreenへpropsで渡した
- 現在のデータを編集フォームのStateの初期値にした
- 新規登録画面と同じ
handleChangeで入力値を変更できるようにした
今回の完成コード
今回変更した
App.jsx、ListScreen.jsx、
EditScreen.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 [editingId, setEditingId] = useState(null);
const editingItem =
items.find((item) => item.id === editingId) ?? null;
const handleCreate = (newItem) => {
// 学習用アプリのため、ブラウザ側でIDを生成
const itemWithId = {
...newItem,
id: crypto.randomUUID(),
};
setItems((prevItems) => [
...prevItems,
itemWithId,
]);
setScreen("list");
};
const startEdit = (id) => {
setEditingId(id);
setScreen("edit");
};
if (screen === "create") {
return (
<CreateScreen
onCreate={handleCreate}
onBack={() => setScreen("list")}
/>
);
}
if (screen === "edit" && editingItem) {
return (
<EditScreen
item={editingItem}
onBack={() => setScreen("list")}
/>
);
}
return (
<ListScreen
items={items}
onCreate={() => setScreen("create")}
onEdit={startEdit}
/>
);
}
src/components/ListScreen.jsx
export default function ListScreen({ items, onCreate, onEdit }) {
return (
<main className="list-screen">
<header className="list-header">
<h1>気になるものリスト</h1>
<div className="header-actions">
<button type="button" onClick={onCreate}>
新規登録
</button>
</div>
</header>
<table>
<thead>
<tr>
<th>名前</th>
<th>カテゴリ</th>
<th>ステータス</th>
<th>価格</th>
<th>メモ</th>
<th>操作</th>
</tr>
</thead>
<tbody>
{items.map((item) => (
<tr key={item.id}>
<td>{item.name}</td>
<td>{item.category}</td>
<td>{item.status}</td>
<td>{item.price.toLocaleString()}円</td>
<td>{item.memo}</td>
<td>
<button
type="button"
onClick={() => onEdit(item.id)}
>
編集
</button>
</td>
</tr>
))}
</tbody>
</table>
</main>
);
}
src/components/EditScreen.jsx
import { useState } from "react";
import { categories, statuses } from "../options";
export default function EditScreen({ item, onBack }) {
const [inputValues, setInputValues] = useState({
name: item.name,
category: item.category,
status: item.status,
price: String(item.price),
memo: item.memo,
});
const handleChange = (event) => {
const fieldName = event.target.name;
const value = event.target.value;
const newInputValues = { ...inputValues };
newInputValues[fieldName] = value;
setInputValues(newInputValues);
};
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 edit-title">編集画面</h2>
<form>
<label>
名前
<input
name="name"
value={inputValues.name}
onChange={handleChange}
/>
</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>
</form>
</main>
);
}
アプリの完成状況
- ✅ Reactを動かし、JSXを表示する
- ✅ Stateで画面を切り替える
- ✅ 1件のデータを表示する
- ✅ 複数のデータを一覧表示する
- ✅ コンポーネントとpropsを使う
- ✅ コンポーネントを別ファイルへ分ける
- ✅ 新規登録画面を作る
- ✅ 一覧データをStateで管理する
- ✅ 編集画面を作る
- ⬜ データを更新する
- ⬜ データを削除する
- ⬜ カテゴリ・ステータスで絞り込む
- ⬜ キーワードで検索する
- ⬜ localStorageで保存して完成