useLocalStorage Hydration Error

sumi-0011·2023년 6월 28일
0

🔥 트러블슈팅

목록 보기
8/13

error message

전형적인 Hydration error가 발생하였다.

내가 실행하는 환경은 Next.js로 SSR이 가능하기 때문에
처음 코드가 실행될 때 client side가 아닌 server side에서 실행될 수도 있다.

local storage는 client side에서만 실행이 가능하기 때문에
(server side에는 window 객체가 없기 때문에)
client side에서만 실행이 가능하도록 수정해주어야 한다.

Error: Hydration failed because the initial UI does not match what was rendered on the server.
Warning: Expected server HTML to contain a matching <div> in <div>.

hook을 client side에서만 실행하기

mounted 된 순간부터 client side이다.
useEffect + deps []에 실행되는 시점이 처음으로 mounted되는 시점이기 때문에
mounted를 체크하는 상태를 추가해주면 된다

const [isMounted, setMounted] = useState(false);

useEffect(() => {
  setMounted(true);
},[])

isMounted의 초기 상태는 false 여야 한다.
mounted 상태를 useState로 관리해야한다.
만약 useRef로 관리하게 된다면, isMounted가 바뀌어도 화면이 렌더링 되지 않기 때문이다.

실제로 hook을 수정하기

기존 코드

import { useState } from 'react';
/**
 * @description 페이지 새로 고침을 통해 상태가 유지되도록 로컬 저장소에 동기화합니다.
 *
 * @param key 로컬 저장소에 저장될 키
 * @param initialValue 초기 값
 * @returns [storedValue, setValue] - 로컬 저장소에 저장된 값, 저장 함수
 */
function useLocalStorage<T>(key: string, initialValue: T) {
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    try {
      const item = window.localStorage.getItem(key);

      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);

      return initialValue;
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.error(error);
    }
  };

  return [storedValue, setValue] as const;
}

export default useLocalStorage;

수정 코드

기존 코드에서 isMounted를 체크하는 코드만을 추가하면 된다.

import { useEffect, useState } from 'react';

/**
 * @description 페이지 새로 고침을 통해 상태가 유지되도록 로컬 저장소에 동기화합니다.
 *
 * @param key 로컬 저장소에 저장될 키
 * @param initialValue 초기 값
 * @returns [storedValue, setValue] - 로컬 저장소에 저장된 값, 저장 함수
 */
function useLocalStorage<T>(key: string, initialValue: T) {
  // State to store our value
  // Pass initial state function to useState so logic is only executed once
  const [storedValue, setStoredValue] = useState<T>(() => {
    if (typeof window === 'undefined') {
      return initialValue;
    }
    try {
      const item = window.localStorage.getItem(key);

      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.error(error);

      return initialValue;
    }
  });

  const setValue = (value: T | ((val: T) => T)) => {
    try {
      const valueToStore = value instanceof Function ? value(storedValue) : value;
      setStoredValue(valueToStore);
      if (typeof window !== 'undefined') {
        window.localStorage.setItem(key, JSON.stringify(valueToStore));
      }
    } catch (error) {
      console.error(error);
    }
  };

  const [isMounted, setIsMounted] = useState(false);

  useEffect(() => {
    setIsMounted(true);
  }, []);

  if (isMounted) {
    return [storedValue, setValue] as const;
  }

  return [initialValue, setValue] as const;
}

export default useLocalStorage;

참고

profile
안녕하세요 😚

0개의 댓글