SPA はリロードすると state が消えてしまうので永続化したい状態ってのが結構あります
生の React だと カスタム hook や useEffect
内で localStorage にアクセスして保存させたえい、Recoil でも effect
を使って永続化するコードを書く必要がありました
jotai を使うと localStorage に保存するための API が用意されていて永続化がめちゃくちゃ簡単だったのでメモに残しておきます
環境
- React
18.2.0
- Next.js
13.2.4
- jotai
2.0.3
- TypeScript
5.0.2
atomWithStorage で直接 localStorage に state を保存できる!
atomWithStorage(key, initialValue, storage)
key
(required): a unique string used as the key when syncing state with localStorage, sessionStorage, or AsyncStorageinitialValue
(required): the initial value of the atomstorage
(optional): an object with:
getItem
,setItem
andremoveItem
methods for storing/retrieving/deleting persisted state; defaults to usinglocalStorage
for storage/retrieval andJSON.stringify()
/JSON.parse()
for serialization/deserialization.- Optionally, the storage has a
subscribe
property, which can be used to synchronize storage. The defaultlocalStorage
handlesstorage
events for cross-tab synchronization.cf. Storage — Jotai, primitive and flexible state management for React
auth.atom.ts
import { atomWithStorage } from 'jotai/utils'; export const isLoggedIn = true as const; export const unLoggedIn = false as const; const beforeLoginCheck = undefined; type AuthType = typeof isLoggedIn | typeof unLoggedIn | typeof beforeLoginCheck; // "auth" という key で localStorage に保存される // 保存される値は自動的に JSON.stringify() される export const authAtom = atomWithStorage<AuthType>('auth', beforeLoginCheck);
localStorage に auth
という key で保存する state を作成
useAuth.ts
import { useCallback } from 'react'; import { useAtomValue, useSetAtom } from 'jotai'; import { RESET } from 'jotai/utils'; import { authAtom, isLoggedIn, unLoggedIn } from './auth.atom'; export const useAuth = () => { return useAtomValue(authAtom); }; export const useAuthMutators = () => { const setAuth = useSetAtom(authAtom); const clearAuth = useCallback(() => { // RESET を渡すと localStorage から削除される setAuth(RESET); }, [setAuth]); const login = useCallback(() => { setAuth(isLoggedIn); }, [setAuth]); const loginError = useCallbacl(() => { setAuth(unLoggedIn); }, [setAuth]); return {clearAuth, login, loginError}; };
useAuth()
は localStorage から取り出した値を返すだけの hook。
※ localStorage に値が存在しない場合は atom 作成時の initialValue
が返される
useAuthMutators()
は localStorage から delteItem
で永続化した state を完全に削除しうてしまう clearAuth
と 値を更新して localStorage に保存する login
と loginError
を提供するようにした
コンポーネントでの使用
import { useCallback } from 'react'; import { useAuthMutators } from './useAuth'; function LogoutButton(): JSX.Element { const { clearAuth } = useAuthMutators(); const handleLogout = useCallback(() => { clearAuth() }, [clearAuth]); return <button type="button" onClick={handleLogout}>Logout</button> } function LoginForm(): JSX.Element { const { login, loginError } = useAuthMutators(); const handleSubmit = useCallback(async (evt: FormEvent<HTMLFormElement>) => { evt.preventDefault(); // 略 try { const isLogin = await postLoginForm(data); login(); } catch (error) { loginError(); } }, [login, loginError]); return <form onSubmit={handleSubmit}>{/*略*/}</form>; } export { LogoutButton, LoginForm };
👇
import { useAuth } from './useAuth'; import { LoginForm, LogoutButton } from './MyComponents'; function MyApp(): JSX.Element { const auth = useAuth(); if ( !auth ) { return <LoginForm />; } return <LogoutButton />; }
ログインに成功すると localStorage に {"auth": "true"}
が保存され、ログアウトすると localStorage から state が削除される
所管
jotai を使えば localStorage にアクセスする部分を意識することなく localStorage で状態の永続化ができました!
また、リロードの瞬間など localStorage にアクセスできない時は atom 作成時の initialValue
が返されるようになっていて Next.js で使う場合も localStorage にアクセスできないサーバーサイドを意識することなくそのまま使うことができました
initialValue
を工夫したり <Suspense>
と併用することでリロード時だけ loader を挟むなどもできそうです!
👻 jotai のメモ
[参考]