一覧データをStateで管理しよう

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

今回やること

前回は、新規登録画面のフォームに入力した内容から newItemを作りました。

今回は、そのnewItemCreateScreenから親の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なので、 まずはnewItemAppへ渡します。

今回は少し処理の流れが複雑なので、 まず全体の流れを確認しておきましょう。

①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に渡す

次に、新規登録画面を表示する部分で、 handleCreateonCreateというpropsとして渡します。

if (screen === "create") {
  return (
    <CreateScreen
      onCreate={handleCreate}
      onBack={() => setScreen("list")}
    />
  );
}

これで、AppからCreateScreenhandleCreateを渡せるようになりました。

③ 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が渡されるため、 ApphandleCreate(newItem)で受け取れます。

ConsoleでnewItemを確認しよう

フォームに値を入力して「登録する」を押し、 Consoleを確認しましょう。
「Appコンポーネントです」と newItemが表示されれば、 CreateScreenからAppへ データを渡せています。

確認できたら、 console.log()は次の処理へ置き換えます。

3. 追加するデータにIDを付けよう

引き続き、src/App.jsxを編集します。

先ほど作ったhandleCreateconsole.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へ追加する

handleCreateitemWithIdを作る処理の下に、 次のコードを追加します。

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からonCreatenewItemを渡した
  • ApphandleCreatenewItemを受け取った
  • crypto.randomUUID()で新しいIDを作った
  • newItemをコピーし、IDを追加したitemWithIdを作った
  • setItemsで新しいデータを一覧へ追加した
  • Stateの配列を直接変更せず、新しい配列を作って更新した
  • 登録後に一覧画面へ戻した

今回の完成コード

今回変更した App.jsxCreateScreen.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で保存して完成
スポンサーリンク