- Published on
next.js + recoilでデータを永続化する方法
利便性を高めるためにdataを永続化したい時があります。 今回は、next.jsとrecoilを使っていて、dataを永続化させる方法を紹介します。
最初はrecoil-persist
を使っていたのですが、recoil-persist
を使って、nextjsを使う場合は、ドキュメントに書いているやり方だと Error: Hydration failed because the initial UI does not match what was rendered on the server.
のエラーが出てしまいます。
このエラーはサーバとclientで初回にレンダリングされる内容が違うことで起きます。そのため、サーバとclientで初回にレンダリングされる内容を同一にするための処理を入れないといけません。
解決方法としては、下記のようにatomのuseRecoilState
を直接提供せずに、custom hooksを使ってdomがmountされた後に、recoilの値を参照するようにしないといけません。
import { useEffect, useState } from 'react' import { atom, useRecoilState } from "recoil" import { recoilPersist } from "recoil-persist" const { persistAtom } = recoilPersist() const DEFAULT_EXAMPLE_STATE = undefined export const exampleState = atom<string | undefined>({ key: "exampleState", default: DEFAULT_EXAMPLE_STATE, effects: [persistAtom] }); export function useExampleState() { const [didMount, setDidMount] = useState(false); const [example, setExample] = useRecoilState(exampleState) React.useEffect(() => { setDidMount(true); }, []); return [ didMount ? example : DEFAULT_EXAMPLE_STATE, setExample ] }
あるいは、recoil-persist
を使わずともlocalstorageに簡単に保存することができます。
import { useEffect, useState } from 'react' import { atom, useRecoilState } from "recoil" import { recoilPersist } from "recoil-persist" const { persistAtom } = recoilPersist() const localStorageEffect = key => ({setSelf, onSet}) => { if (typeof window == 'undefined') { return } const savedValue = localStorage.getItem(key) if (savedValue != null) { setSelf(JSON.parse(savedValue)); } onSet((newValue, _, isReset) => { isReset ? localStorage.removeItem(key) : localStorage.setItem(key, JSON.stringify(newValue)); }); }; const DEFAULT_EXAMPLE_STATE = undefined export const exampleState = atom<string | undefined>({ key: "exampleState", default: DEFAULT_EXAMPLE_STATE, effects: [localStorageEffect('exampleState')] }); export function useExampleState() { const [didMount, setDidMount] = useState(false); const [example, setExample] = useRecoilState(exampleState) React.useEffect(() => { setDidMount(true); }, []); return [ didMount ? example : DEFAULT_EXAMPLE_STATE, setExample ] }
参考: useEffect#displaying-different-content-on-the-server-and-the-client