今回やること
前回までで、
一覧表示・新規登録・編集・更新・削除・絞り込み・キーワード検索が
できるようになりました。
ただし、今のアプリには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に保存データがあればそのデータを、
まだ保存されていなければinitialItemsを
items 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が返されるため、
これまで使ってきたinitialItemsを
items 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を作ります。
次に、現在のitemsにitemWithIdを追加した
新しい配列newItemsを作ります。
その同じnewItemsを
setItemsへ渡してStateを更新し、
localStorageにも保存します。
ここで実際に確認してみましょう。
- 新しいデータを1件登録する
- 一覧に追加されたことを確認する
- ブラウザを再読み込みする
再読み込みしても、
登録したデータが一覧に残っていれば成功です。
これで、新規登録したデータを再読み込み後も残せるようになりました。
更新したデータを保存する
更新処理も同じ考え方です。
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件編集してみましょう。
- データを編集する
- 変更した内容が一覧へ反映されたことを確認する
- ブラウザを再読み込みする
再読み込みしても、
編集した内容が残っていれば成功です。
更新した内容も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件削除する
- 一覧からなくなったことを確認する
- ブラウザを再読み込みする
再読み込みしても、
削除したデータが戻ってこなければ成功です。
これで、新規登録・更新・削除のすべてを保存できるようになりました。
今回は操作ごとに保存する
今回は、
新規登録・更新・削除のそれぞれで
localStorageを更新しました。
新規登録
handleCreate
↓
Stateを更新
↓
localStorageへ保存
更新
handleUpdate
↓
Stateを更新
↓
localStorageへ保存
削除
handleDelete
↓
Stateを更新
↓
localStorageへ保存
今回localStorageへ保存しているのは、
一覧データのitemsだけです。
画面の切り替えや検索条件、フォームの入力途中の値は、
再読み込み後まで残す必要がないため保存していません。
ReactとJavaScriptを分けて見てみよう
今回登場した内容を、
ReactとJavaScriptに分けて整理します。
今回登場したReactとJavaScriptの要素
| 種類 |
今回登場したもの |
| React |
useState、useEffect、Stateの更新 |
| JavaScript・ブラウザ |
localStorage、JSON.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で保存して完成