編集画面を作ろう

← 第9
Reactアプリ作成(JS編)10 / 15
11回 →

今回やること

前回は、新規登録画面で作ったデータを 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.ideditingIdが 一致するデータを探しています。

たとえばeditingId"sample-2"なら、 id"sample-2"のデータが editingItemに入ります。

条件に一致するデータが見つからない場合、 find()undefinedを返します。
今回は?? nullを付けて、 見つからなかった場合はnullにしています。

まだeditingIdにはnullが入っているため、 この時点のeditingItemnullです。
次に、一覧から編集するデータを選べるようにします。

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.jsxListScreen.jsxEditScreen.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で保存して完成
スポンサーリンク