Next.js use localstorage problem with SSR
Asked Answered
O

3

10

I have the following custom hook which stores data in the localstorage:

import { useCallback, useEffect, useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const initialize = (key) => {
    try {
      const item = localStorage.getItem(key);
      if (item && item !== "undefined") {
        return JSON.parse(item);
      }

      localStorage.setItem(key, JSON.stringify(initialValue));
      return initialValue;
    } catch {
      return initialValue;
    }
  };

  const [state, setState] = useState(() => initialize(key)); // problem is here

  const setValue = useCallback(
    (value) => {
      try {
        const valueToStore = value instanceof Function ? value(storedValue) : value;
        setState(valueToStore);
        localStorage.setItem(key, JSON.stringify(valueToStore));
      } catch (error) {
        console.log(error);
      }
    },
    [key, setState]
  );

  const remove = useCallback(() => {
    try {
      localStorage.removeItem(key);
    } catch {
      console.log(error);
    }
  }, [key]);

  return [state, setValue, remove];
};


It shows the following problem, which i googled it and it seems it due to the fact that Nextjs tries to run the code on the server side and there is no available window object.

enter image description here

The problem seems to come from the line where i try to initialize the stored data:

const [state, setState] = useState(() => initialize(key));

I tried to pack this logic within a useEffect so it only runs on client side, but i got the infinite loop which i couldnt solve.

Occlusive answered 16/8, 2021 at 20:25 Comment(0)
P
9
import { useCallback, useEffect, useState } from "react";

export const useLocalStorage = (key, initialValue) => {
  const initialize = (key) => {
    try {
      const item = localStorage.getItem(key);
      if (item && item !== "undefined") {
        return JSON.parse(item);
      }

      localStorage.setItem(key, JSON.stringify(initialValue));
      return initialValue;
    } catch {
      return initialValue;
    }
  };

  const [state, setState] = useState(null); // problem is here

  // solution is here....
  useEffect(()=>{
    setState(initialize(key));
  },[]);

  const setValue = useCallback(
    (value) => {
      try {
        const valueToStore = value instanceof Function ? value(storedValue) : value;
        setState(valueToStore);
        localStorage.setItem(key, JSON.stringify(valueToStore));
      } catch (error) {
        console.log(error);
      }
    },
    [key, setState]
  );

  const remove = useCallback(() => {
    try {
      localStorage.removeItem(key);
    } catch {
      console.log(error);
    }
  }, [key]);

  return [state, setValue, remove];
};

window,localStorage,sessionStorage,etc.. are not defined on server, so accessing them on server will result as an error. Use useEffect to make sure that, these code will be executed on client side.

Parity answered 17/8, 2021 at 1:26 Comment(6)
React Hook useEffect has missing dependencies: 'initialize' and 'key'. So when i add initialzie method to the depenedncy, it says that the initialize method should use useCallback, so than i made it using that. When i add initialValue to the dependency array of useCallback, i got infinite loop.Occlusive
Do not add anythinffParity
Is it a good practice to leave out dependency?Occlusive
@Occlusive useEffect completely runs on client side, so you don't need to add any dependency.Parity
reactjs.org/docs/…Occlusive
If you want rerender then, otherwise it is completely okeyParity
S
1

Maybe move the initialize to inside a useEffect (useState needs to be kept outside however)

Inside the useEffect, you initialize only if typeof window !== "undefined"

Sigma answered 17/8, 2021 at 1:21 Comment(4)
useEffect only runs on client side, so checking the window should not needed. And it is not the problem. See the comment for the Rahul answer.Occlusive
In next.js, it can run on the server during SSR, like when you are running things locally, no?Sigma
useEffect has a csr(client side rendering) mechanism, its not different if you are using in ssg or ssr pre-renderKeto
Ok, I get it now, thanks for the explanationSigma
L
1

This worked for me with React/NextJS :)

import { useEffect, useState } from 'react';

const isServer = typeof window === 'undefined';

export default function useLocalStorage(key, initialValue) {
  // State to store our value
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState(() => initialValue);

  const initialize = () => {
    if (isServer) {
      return initialValue;
    }
    try {
      // Get from local storage by key
      const item = window.localStorage.getItem(key);
      // Parse stored json or if none return initialValue
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      // If error also return initialValue
      console.log(error);
      return initialValue;
    }
  };

  /* prevents hydration error so that state is only initialized after server is defined */
  useEffect(() => {
    if (!isServer) {
      setStoredValue(initialize());
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, []);

  // Return a wrapped version of useState's setter function that ...
  // ... persists the new value to localStorage.
  const setValue = (value) => {
    try {
      // Allow value to be a function so we have same API as useState
      const valueToStore =
        value instanceof Function ? value(storedValue) : value;
      // Save state
      setStoredValue(valueToStore);
      // Save to local storage
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      // A more advanced implementation would handle the error case
      console.log(error);
    }
  };
  return [storedValue, setValue];
}
Locative answered 28/3, 2023 at 0:33 Comment(1)
Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.Templeton

© 2022 - 2024 — McMap. All rights reserved.