Reactで学ぶ 予定管理アプリ開発(第4回)~useEffect + localStorageによるデータ永続化~

GitHubでつくって学ぶ

はじめに

前回は、useStateを使って予定の追加・削除ができるようにした。ただし、最後に触れた通り、ブラウザを再読み込みすると追加した予定はすべて消えてしまう仕様だった。

今回は、useEffectlocalStorageという2つの仕組みを組み合わせて、ブラウザを閉じたり再読み込みしたりしても、予定のデータが消えないようにする。

今回もツールが生成したpackage.jsonなどのファイルは変更していない。読むべきは、この後出てくるApp.jsxだけである(components/配下の2ファイルはVer.3から変更なし)。

今回やること

  • localStorageという、ブラウザにデータを保存できる仕組みを知る
  • useEffectを使って、状態(state)が変化するたびにlocalStorageへ保存する
  • ページを開いた時に、localStorageから保存済みのデータを読み込む

localStorageとは

localStorageは、ブラウザが持っている「保存箱」のようなものである。ブラウザを閉じても、パソコンを再起動しても、データは消えずに残り続ける(サイトのデータを手動で消さない限り)。

使い方はシンプルで、以下の2つの操作が基本になる。

// 保存する
localStorage.setItem('キー', '値')

// 取り出す
localStorage.getItem('キー')

ただし一つ注意点がある。localStorageに保存できるのは文字列だけである。今回扱いたい予定データは配列(オブジェクトの集まり)なので、そのままでは保存できない。そこで、以下のように変換する。

  • 保存する時:JSON.stringify(データ) で、配列やオブジェクトを文字列に変換する
  • 取り出す時:JSON.parse(文字列) で、文字列を元の配列やオブジェクトに戻す

useEffectとは

useEffectは、「何かの値が変化した後に、決まった処理を実行する」ためのReactの機能(Hook)である。

useEffect(() => {
  localStorage.setItem(STORAGE_KEY, JSON.stringify(schedules))
}, [schedules])
  • 第1引数:実行したい処理(ここではlocalStorageへの保存)
  • 第2引数([schedules]):この配列に入れた値が変化したタイミングで、処理が実行される。schedulesが変わるたび=予定が追加・削除されるたびに、自動で保存される、という仕組みである

ページを開いた時にデータを読み込む

保存はできても、次にページを開いた時に読み込む処理がなければ意味がない。今回は、useStateの初期値に関数を渡す形で対応する。

src/App.jsx(抜粋)

function loadSchedules() {
  const saved = localStorage.getItem(STORAGE_KEY)

  if (!saved) return initialSchedules

  try {
    return JSON.parse(saved)
  } catch {
    return initialSchedules
  }
}

function App() {
  const [schedules, setSchedules] = useState(loadSchedules)
  ...

useState(loadSchedules)のように関数を渡すと、その関数は最初の1回だけ実行され、戻り値が初期値として使われる。今回は「localStorageに保存済みのデータがあればそれを使い、なければ元々の初期データ(initialSchedules)を使う」という関数にしてある。

try / catchを入れているのは、万が一localStorageの中身が壊れたデータ(正しいJSON形式でない)だった場合に、アプリごとエラーで止まってしまうのを防ぐためである。

App.jsx全体

今回変更したApp.jsxの全体は以下の通りである。

src/App.jsx

import { useEffect, useState } from 'react'
import './App.css'
import ScheduleItem from './components/ScheduleItem'
import ScheduleForm from './components/ScheduleForm'

const STORAGE_KEY = 'pc-labo-schedules'

const initialSchedules = [
  { id: 1, date: '2026-07-20', title: 'Reactの環境構築を復習する' },
  { id: 2, date: '2026-07-21', title: 'コンポーネント分割の記事を書く' },
  { id: 3, date: '2026-07-22', title: 'propsの動きを検証する' },
]

let nextId = initialSchedules.length + 1

function loadSchedules() {
  const saved = localStorage.getItem(STORAGE_KEY)

  if (!saved) return initialSchedules

  try {
    return JSON.parse(saved)
  } catch {
    return initialSchedules
  }
}

function App() {
  const [schedules, setSchedules] = useState(loadSchedules)

  useEffect(() => {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(schedules))
  }, [schedules])

  const handleAdd = ({ date, title }) => {
    const newSchedule = { id: nextId, date, title }
    nextId += 1
    setSchedules((prev) => [...prev, newSchedule])
  }

  const handleDelete = (id) => {
    setSchedules((prev) => prev.filter((schedule) => schedule.id !== id))
  }

  return (
    <div className="app">
      <h1>予定管理アプリ</h1>
      <p>Ver.4:useEffect + localStorageによるデータ永続化</p>

      <ScheduleForm onAdd={handleAdd} />

      {schedules.length === 0 ? (
        <p className="schedule-empty">予定はまだ登録されていない。</p>
      ) : (
        <ul className="schedule-list">
          {schedules.map((schedule) => (
            <ScheduleItem
              key={schedule.id}
              title={schedule.title}
              date={schedule.date}
              onDelete={() => handleDelete(schedule.id)}
            />
          ))}
        </ul>
      )}
    </div>
  )
}

export default App

Ver.3から変わったのは、useEffectの追加と、初期値をloadSchedules関数に差し替えた部分だけである。追加・削除の処理(handleAddhandleDelete)自体はVer.3のままで、何も変更していない。

実行結果

npm run devで開発サーバーを起動し、ブラウザで確認する。終了する場合はターミナル上でCtrl + Cを押す(Ctrl + Zは一時停止するだけなので使わない)。

予定を追加・削除した後、以下のいずれの方法でも、内容が消えずに残っていることを確認できる。

  • 再読み込みマークをクリックする
  • タブを閉じて、また同じURLを開き直す
  • ブラウザ自体を完全に終了し、再起動してから開き直す

localStorageは「タブやセッション単位」ではなく「ブラウザ+サイトの組み合わせ単位」でデータを保持する仕組みなので、タブを閉じるだけでは消えない。

ただし、以下のケースでは例外的にデータが残らない。

  • シークレットモード/プライベートブラウジングで開いていた場合:ウィンドウを閉じると、そのモード中に保存したデータごと消える
  • ブラウザの「閲覧データを削除」機能でサイトデータを消した場合
  • 別のブラウザ別のパソコンで開いた場合(localStorageはブラウザ単位なので、Chromeで保存したデータをFirefoxで見ることはできない)

今回のまとめ

  • localStorageを使うと、ブラウザにデータを保存でき、再読み込みしても消えなくなる(タブを閉じても、ブラウザを再起動しても保持される)
  • 保存できるのは文字列のみなので、配列やオブジェクトはJSON.stringify / JSON.parseで変換する
  • useEffectを使うと、状態が変化するたびに任意の処理(今回は保存)を自動的に実行できる
  • useStateの初期値には関数を渡せて、最初の1回だけ実行される。ここではページを開いた時の読み込み処理に活用した

これで、予定管理アプリとしての基本機能(追加・削除・保存)は一通り揃った。次回は、一覧表示だけだった画面に、月表示のカレンダーを追加していく。

ソースコード

今回のソースコードは、私のGitHubで公開している。以下のリンクを参照していたただきたい。

GitHub:https://github.com/HappyTalk10/pc-labo-react-learning/tree/main/04_useeffect-localstorage


コメント

タイトルとURLをコピーしました