Published on

next.js + recoilでデータを永続化する方法

Authors
  • avatar
    Name
    ssu
    Twitter

利便性を高めるために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

参考: atom-effects/#local-storage-persistence