Noob
Noob

Reputation: 1014

localStorage is not defined - Recoil with Next.js

I'm using a localstorage effect as per the Recoil docs, but when i run my app i get a localStorage is not defined error in the browser. My first assumption would be that this is not running in the browser and the server instead. I don't think this is the case, unless Next.js is doing something funky under the hood?

Here's my code:

project/recoil/atom.ts

const localStorageEffect = (key: string) => ({setSelf, onSet}: {setSelf: any, onSet: any}) => {
  const savedValue = localStorage.getItem(key)
  if (savedValue != null) {
    setSelf(JSON.parse(savedValue));
  }

  onSet((newValue: any, _: null, isReset: any) => {
    isReset
      ? localStorage.removeItem(key)
      : localStorage.setItem(key, JSON.stringify(newValue));
  });
};

export const userAtom = atom({
  key: 'userAtom',
  default: {},
  effects: [
    localStorageEffect('user'),
  ]
});

I'm trying to use it to persist login state. Here's one example of where I'm using it:

project/pages/login.tsx

const LoginPage = () => {
  const [address, setAddress] = useState('');
  const setUser = useSetRecoilState(userAtom);
  const setIsAuthed = useSetRecoilState(isAuthedAtom);

  const submitHandler = async (e: any) => {
    try {
      const walletInfo = await fetchWallet(address);
      setIsAuthed(true);
      setUser({...walletInfo, address});
      
    } catch(e: any) {
      console.log(e);
    }
  };

  return (
    <Login address={address} setAddress={setAddress} submitHandler={submitHandler} />
  )

Here's _app.tsx:

import type { AppProps } from 'next/app';
import {
  RecoilRoot
} from 'recoil';
import '../styles/globals.css';

function MyApp({ Component, pageProps }: AppProps) {
  return (
    <RecoilRoot>
      <Component {...pageProps} />
    </RecoilRoot>
  );
}

export default MyApp;

However, upon loading the homepage nothing will render because of the error message. I've read that a solution would be to run this in a useEffect hook, however i cannot do this since my effect is defined in atoms.ts and cannot be moved. Is there a way to force this file to ran in the browser?

Upvotes: 2

Views: 2326

Answers (2)

Hayyaun
Hayyaun

Reputation: 316

You don't need to install any other libraries, just replace this part:

import { AtomEffect } from 'recoil';

const store = typeof window !== 'undefined' ? window.localStorage : null;

export const localStorageEffect: (key: string) => AtomEffect<any> =
  (key) =>
  ({ setSelf, onSet }) => {
    if (store) {
      const savedValue = store.getItem(key);
      if (savedValue != null) {
        setSelf(JSON.parse(savedValue));
      }

      onSet((newValue, _, isReset) => {
        isReset ? store.removeItem(key) : store.setItem(key, JSON.stringify(newValue));
      });
    }
  };

Upvotes: 2

Ejiroghene Egbedi
Ejiroghene Egbedi

Reputation: 31

Here you can try this. Worked for me

import { recoilPersist } from 'recoil-persist'


const localStorage = typeof window !== `undefined` ? window.localStorage : null

const { persistAtom } = recoilPersist({
    key: 'recoil-persist',
    storage: localStorage
})

export const darkModeState = atom<true | false >({
    key: 'darkMode',
    default: false,
    effects_UNSTABLE: [persistAtom]
})

Upvotes: 3

Related Questions