localStorageで保存してアプリを完成させよう!

← 第14
Reactアプリ作成(JS編)15 / 15

今回やること

前回までで、 一覧表示・新規登録・編集・更新・削除・絞り込み・キーワード検索が できるようになりました。

ただし、今のアプリには1つ問題があります。

新しいデータを登録したり編集したりしても、 ページを再読み込みすると最初のデータへ戻ってしまいます。

今回は、一覧データをブラウザの localStorageへ保存して、 再読み込みしてもデータが残るようにします。

アプリを開く
↓
localStorageからデータを読み込む
↓
items Stateへ入れる

新規登録・更新・削除
↓
items Stateを更新する
↓
localStorageにも保存する

これができれば、 「気になるものリスト」は完成です。

スポンサーリンク

localStorageとは?

localStorageは、 ブラウザにデータを保存するための仕組みです。

保存したデータは、 ページを再読み込みしたりブラウザを閉じたりしても残ります。

今回のアプリでは、 localStorage一覧データの保存先として使います。

localStorageへ保存できる値は文字列です。

今回のitemsは配列なので、 保存するときはJSON.stringify()を使って文字列へ変換します。

JSON.stringify(items)

反対に、保存した文字列を配列へ戻すときは JSON.parse()を使います。

JSON.parse(savedItems)
items
↓
JSON.stringify()
↓
文字列としてlocalStorageへ保存

保存した文字列
↓
JSON.parse()
↓
配列へ戻す

保存するときの名前を決める

localStorageでは、 保存するデータに名前を付けて管理します。

src/App.jsxを開き、 import文の下、Appコンポーネントの外側に STORAGE_KEYを追加してください。

const STORAGE_KEY = "kininaru-list-items";

今回は、 「気になるものリスト」の一覧データを "kininaru-list-items"という名前で保存します。

この値は読み込みと保存の両方で使うため、 STORAGE_KEYへまとめています。

itemsの初期値を空の配列にする

現在のitems Stateは、 initialItemsを初期値にしています。

const [items, setItems] = useState(initialItems);

今回は、 アプリを表示したあとにlocalStorageから一覧データを読み込むため、 最初は空の配列に変更します。

const [items, setItems] = useState([]);

このあと、 localStorageに保存データがあればそのデータを、 まだ保存されていなければinitialItemsitems Stateへ設定します。

useEffectで保存データを読み込む

次に、 アプリを表示したときにlocalStorageから一覧データを読み込みます。

ここでは、 ReactのuseEffectを使います。

まず、 App.jsxのimport文を次のように変更してください。

import { useEffect, useState } from "react";

次に、 Stateを定義している部分の下へ、 次のコードを追加してください。

useEffect(() => {
  try {
    const savedItems = localStorage.getItem(STORAGE_KEY);

    if (savedItems === null) {
      setItems(initialItems);
      return;
    }

    const parsedItems = JSON.parse(savedItems);

    setItems(
      Array.isArray(parsedItems)
        ? parsedItems
        : initialItems
    );
  } catch {
    setItems(initialItems);
  }
}, []);

useEffectは、 Reactの画面表示とは別に行いたい処理を実行するときに使います。

今回は、 アプリを最初に表示したあとに localStorageからデータを読み込んでいます。

const savedItems = localStorage.getItem(STORAGE_KEY);

localStorage.getItem()で、 STORAGE_KEYという名前で保存されている値を取得します。

まだ何も保存されていない場合は nullが返されるため、 これまで使ってきたinitialItemsitems Stateへ設定します。

if (savedItems === null) {
  setItems(initialItems);
  return;
}

保存データがある場合は、 JSON.parse()で文字列から元のデータへ戻します。

const parsedItems = JSON.parse(savedItems);

今回のitemsは配列なので、 Array.isArray()で配列になっていることも確認しています。

保存データを正しく読み込めなかった場合も、 initialItemsを使います。

最後の[]は、 このEffectを最初の表示後に実行するために指定しています。

}, []);

新規登録したデータを保存する

localStorageから一覧データを読み込めるようになりました。

次は、 新規登録したデータをlocalStorageにも保存します。

Appコンポーネントの handleCreateを、 次のように変更してください。

const handleCreate = (newItem) => {
  // 学習用アプリのため、ブラウザ側でIDを生成
  const itemWithId = {
    ...newItem,
    id: crypto.randomUUID(),
  };

  const newItems = [
    ...items,
    itemWithId,
  ];

  setItems(newItems);

  localStorage.setItem(
    STORAGE_KEY,
    JSON.stringify(newItems)
  );

  setScreen("list");
};

まず、newItemをコピーしてIDを追加した itemWithIdを作ります。 次に、現在のitemsitemWithIdを追加した 新しい配列newItemsを作ります。

その同じnewItemssetItemsへ渡してStateを更新し、 localStorageにも保存します。

ここで実際に確認してみましょう。

  1. 新しいデータを1件登録する
  2. 一覧に追加されたことを確認する
  3. ブラウザを再読み込みする

再読み込みしても、 登録したデータが一覧に残っていれば成功です。

これで、新規登録したデータを再読み込み後も残せるようになりました。

更新したデータを保存する

更新処理も同じ考え方です。

Appコンポーネントの handleUpdateを、 次のように変更してください。

const handleUpdate = (updatedItem) => {
  const newItems = items.map((item) =>
    item.id === updatedItem.id
      ? updatedItem
      : item
  );

  setItems(newItems);

  localStorage.setItem(
    STORAGE_KEY,
    JSON.stringify(newItems)
  );

  setEditingId(null);
  setScreen("list");
};

第11回と同じように、 map()で更新する1件だけを置き換えた newItemsを作ります。

その配列をStateへ設定し、 localStorageにも保存します。

今度は、 一覧のデータを1件編集してみましょう。

  1. データを編集する
  2. 変更した内容が一覧へ反映されたことを確認する
  3. ブラウザを再読み込みする

再読み込みしても、 編集した内容が残っていれば成功です。

更新した内容もlocalStorageへ保存できました。

削除した結果を保存する

最後に、 削除したあともlocalStorageを更新します。

Appコンポーネントの handleDeleteを、 次のように変更してください。

const handleDelete = (id) => {
  const shouldDelete = window.confirm(
    "この項目を削除しますか?"
  );

  if (!shouldDelete) {
    return;
  }

  const newItems = items.filter(
    (item) => item.id !== id
  );

  setItems(newItems);

  localStorage.setItem(
    STORAGE_KEY,
    JSON.stringify(newItems)
  );

  setEditingId(null);
  setScreen("list");
};

第12回と同じように、 filter()で削除するデータを除いた newItemsを作っています。

その配列をStateへ設定し、 localStorageにも保存します。

最後に削除も確認してみましょう。

  1. データを1件削除する
  2. 一覧からなくなったことを確認する
  3. ブラウザを再読み込みする

再読み込みしても、 削除したデータが戻ってこなければ成功です。

これで、新規登録・更新・削除のすべてを保存できるようになりました。

今回は操作ごとに保存する

今回は、 新規登録・更新・削除のそれぞれで localStorageを更新しました。

新規登録
handleCreate
↓
Stateを更新
↓
localStorageへ保存

更新
handleUpdate
↓
Stateを更新
↓
localStorageへ保存

削除
handleDelete
↓
Stateを更新
↓
localStorageへ保存

今回localStorageへ保存しているのは、 一覧データのitemsだけです。

画面の切り替えや検索条件、フォームの入力途中の値は、 再読み込み後まで残す必要がないため保存していません。

ReactとJavaScriptを分けて見てみよう

今回登場した内容を、 ReactとJavaScriptに分けて整理します。

今回登場したReactとJavaScriptの要素
種類 今回登場したもの
React useStateuseEffect、Stateの更新
JavaScript・ブラウザ localStorageJSON.stringify()JSON.parse()Array.isArray()try...catch

useEffectで最初のデータを読み込み、 setItemsで一覧データをStateへ反映する部分はReactです。

一方、 localStorageへの保存・読み込みや、 JSONへの変換はReactではなく JavaScriptやブラウザが提供する機能です。

今回のまとめ

  • localStorageを一覧データの保存先として使った
  • items Stateの初期値を空の配列にした
  • useEffectでアプリ表示後に保存データを読み込んだ
  • 保存データがない場合はinitialItemsを使った
  • JSON.stringify()で配列を文字列へ変換して保存した
  • JSON.parse()で保存した文字列を元のデータへ戻した
  • 新規登録・更新・削除のそれぞれでlocalStorageを更新した
  • 各操作のあとに再読み込みして、保存できていることを確認した

今回の完成コード

今回変更した App.jsxの完成コードです。

src/App.jsx

import { useEffect, useState } from "react";

import CreateScreen from "./components/CreateScreen";
import EditScreen from "./components/EditScreen";
import ListScreen from "./components/ListScreen";
import { initialItems } from "./initialItems";

const STORAGE_KEY = "kininaru-list-items";

export default function App() {
  const [items, setItems] = useState([]);
  const [screen, setScreen] = useState("list");
  const [editingId, setEditingId] = useState(null);

  const editingItem =
    items.find((item) => item.id === editingId) ?? null;

  useEffect(() => {
    try {
      const savedItems = localStorage.getItem(STORAGE_KEY);

      if (savedItems === null) {
        setItems(initialItems);
        return;
      }

      const parsedItems = JSON.parse(savedItems);

      setItems(
        Array.isArray(parsedItems)
          ? parsedItems
          : initialItems
      );
    } catch {
      setItems(initialItems);
    }
  }, []);

  const handleCreate = (newItem) => {
    // 学習用アプリのため、ブラウザ側でIDを生成
    const itemWithId = {
      ...newItem,
      id: crypto.randomUUID(),
    };

    const newItems = [
      ...items,
      itemWithId,
    ];

    setItems(newItems);

    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify(newItems)
    );

    setScreen("list");
  };

  const startEdit = (id) => {
    setEditingId(id);
    setScreen("edit");
  };

  const handleUpdate = (updatedItem) => {
    const newItems = items.map((item) =>
      item.id === updatedItem.id
        ? updatedItem
        : item
    );

    setItems(newItems);

    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify(newItems)
    );

    setEditingId(null);
    setScreen("list");
  };

  const handleDelete = (id) => {
    const shouldDelete = window.confirm(
      "この項目を削除しますか?"
    );

    if (!shouldDelete) {
      return;
    }

    const newItems = items.filter(
      (item) => item.id !== id
    );

    setItems(newItems);

    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify(newItems)
    );

    setEditingId(null);
    setScreen("list");
  };

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

  if (screen === "edit" && editingItem) {
    return (
      <EditScreen
        item={editingItem}
        onSave={handleUpdate}
        onDelete={handleDelete}
        onBack={() => setScreen("list")}
      />
    );
  }

  return (
    <ListScreen
      items={items}
      onCreate={() => setScreen("create")}
      onEdit={startEdit}
    />
  );
}

完成したアプリを振り返る

これで、 ReactとJavaScriptで作る 「気になるものリスト」が完成しました。

全15回を通して、 少しずつ次の機能を追加してきました。

  • Reactで画面を表示する
  • Stateで画面を切り替える
  • データを一覧表示する
  • コンポーネントとpropsを使う
  • コードを役割ごとのファイルへ分ける
  • 新しいデータを登録する
  • データを編集・更新する
  • データを削除する
  • カテゴリ・ステータスで絞り込む
  • キーワードで検索する
  • localStorageへ保存する

最初は画面に文字を表示するところから始めましたが、 最後にはデータの追加・更新・削除・検索・保存までできる Reactアプリになりました。

Reactアプリ作成 JS編は、これで完成です!

アプリの完成状況

  • ✅ Reactを動かし、JSXを表示する
  • ✅ Stateで画面を切り替える
  • ✅ 1件のデータを表示する
  • ✅ 複数のデータを一覧表示する
  • ✅ コンポーネントとpropsを使う
  • ✅ コンポーネントを別ファイルへ分ける
  • ✅ 新規登録画面を作る
  • ✅ 一覧データをStateで管理する
  • ✅ 編集画面を作る
  • ✅ データを更新する
  • ✅ データを削除する
  • ✅ カテゴリ・ステータスで絞り込む
  • ✅ キーワードで検索する
  • ✅ localStorageで保存して完成
スポンサーリンク