いまのお気に入りには、大きな弱点があります。リロードすると全部消えます。
stateはメモリの上の値なので、ページを読み直せば初期値に戻ります。ブラウザに残すにはlocalStorageを使います。
localStorageそのものの説明はこちら。文字列しか保存できないところまで含めて、考え方はReactでも同じですまず素直に書いてみる
App.jsxに直接書くと、こうなります。
// 初期値をlocalStorageから読む
const [favoriteIds, setFavoriteIds] = useState(() => {
const saved = localStorage.getItem('zukan-favorites')
return saved ? JSON.parse(saved) : []
})
// 変わるたびに保存する
useEffect(() => {
localStorage.setItem('zukan-favorites', JSON.stringify(favoriteIds))
}, [favoriteIds])2つ、初めて出てくる書き方があります。
useStateに関数を渡す
useState(() => { … })useStateの初期値に関数を渡すと、Reactは最初の1回だけそれを実行します。
useState(localStorage.getItem(…))と書いてしまうと、再描画のたびにlocalStorageを読みにいきます(使われるのは最初の値だけなのに)。読みこみのように少し重い処理は、関数の形で渡します。
依存配列に値を書く
useEffect(() => { … }, [favoriteIds])前回は[](最初の1回だけ)でしたが、今回は[favoriteIds]。お気に入りが変わるたびに保存したいからです。
App.jsxが太ってきた
上のコードを足すと、Appはこうなります。
- 鳥のデータのstate
- 読みこみ中のstate
- 検索語のstate
- お気に入りのstate
- 「お気に入りだけ」のstate
- fetchのEffect
- 保存のEffect
- お気に入りの追加削除の関数
画面の組み立てを読みにきたのに、保存の話が混ざって読みにくい——このときが切り出しどきです。
カスタムフック=Hooksを使う関数
「お気に入りに関すること」だけを別のファイルへ移します。
import { useEffect, useState } from 'react'
const STORAGE_KEY = 'zukan-favorites'
export function useFavorites() {
const [favoriteIds, setFavoriteIds] = useState(() => {
const saved = localStorage.getItem(STORAGE_KEY)
return saved ? JSON.parse(saved) : []
})
useEffect(() => {
localStorage.setItem(STORAGE_KEY, JSON.stringify(favoriteIds))
}, [favoriteIds])
function toggleFavorite(id) {
setFavoriteIds((current) =>
current.includes(id)
? current.filter((favoriteId) => favoriteId !== id)
: [...current, id],
)
}
return { favoriteIds, toggleFavorite }
}使う側は2行になります。
import { useFavorites } from './hooks/useFavorites.js'
const { favoriteIds, toggleFavorite } = useFavorites()中身は、さっきまでApp.jsxにあったコードそのままです。移しただけで、新しい概念は何も足していません。
名前はuseで始める
export function useFavorites() { … } // ○
export function getFavorites() { … } // ✕ 中でHooksは呼べないuseで始まる名前は、Reactと開発ツールにとって「これはHooksを含む」という合図です。この名前でないと、
- Hooksを条件の中で呼んでいても警告してもらえない
- 読む人が「ふつうの関数」だと思って、
ifの中で呼んでしまう
という事故が起きます。中でHooksを呼ぶなら、必ずuseから始める。これは決まりだと思ってください。
状態は共有されない
よくある勘違いです。
// A.jsx
const { favoriteIds } = useFavorites()
// B.jsx
const { favoriteIds } = useFavorites() // ← Aとは別の状態カスタムフックが共有するのは処理(ロジック)であって、状態そのものではありません。呼んだ場所ごとに独立したstateが作られます。
前回の「stateの持ち上げ」と組み合わせて、共通の親で1回だけ呼び、propsで配る——今回もその形にします。
公式ドキュメントも見てみる
ja.react.dev外部サイトカスタムフックでロジックを再利用する – React公式の「カスタムフックでロジックを再利用する」。「カスタムフックはstateそのものではなくstateを扱うロジックを共有する」の節が、この回のいちばん大事なところですこのレッスンのまとめ
- stateはリロードで消える。残すなら
localStorage(保存できるのは文字列だけ) useState(() => …)で初期値の計算を最初の1回だけにする- 依存配列は「いつやり直すか」——
[favoriteIds]なら値が変わるたびに保存 - カスタムフックは
useで始まる、Hooksを使うただの関数 - 共有されるのは処理であって状態ではない——呼んだ場所ごとに独立する
- 切り出すのは役目に名前が付くとき。無理に細かくしない
次からは章が変わって、ページを増やします。一覧と詳細をURLで分けるところから。
