localStorage 값 변경 감지하기

2022년 07월 18일
브라우저를 껐다 켜더라도 값이 유지되어 있는 localStorage는 매우 활용성이 높다.
유저의 마지막 설정 상태를 유지하거나, 유저가 마지막으로 접근했던 탭을 저장하여 웹을 마치 이어서 사용하는 듯한 기분을 주는 등의 기능에 사용 중에 있다.
 
하지만 localStorage는 번거로운 단점이 하나 있는데, 바로 저장을 하기 위해서는 string 형태로 저장을 해야한다는 점이다.
때문에 JavaScript Object를 저장하기 위해서는 JSON 형태로 변환하여 저장을 해야하고, 다른 컴포넌트 등에서 localStorage 값이 변경되었다는 Event를 받지 못하는 점이다.
 
이를 개선하기 위해 간단한 Custom Hook을 만들었다.
 
import React, { useState, useEffect } from 'react'; const useStorage = <T>( key: string, defaultValue: T, removable?: boolean, ): [T, React.Dispatch<React.SetStateAction<T>>] => { const readValue = (): T => { try { const item = localStorage.getItem(key); return item ? (JSON.parse(item) as T) : defaultValue; } catch (error) { console.warn(`Error reading localStorage key “${key}”:`, error); return defaultValue; } }; const [value, setValue] = useState<T>(readValue); useEffect(() => { const storageValue = localStorage.getItem(key); if (storageValue) { setValue(JSON.parse(storageValue) as T); } }, [key]); useEffect(() => { if (!value && removable) { localStorage.removeItem(key); return; } window.dispatchEvent( new StorageEvent('storage', { key, newValue: JSON.stringify(value), }), ); localStorage.setItem(key, JSON.stringify(value)); }, [key, removable, value]); return [value, setValue]; }; export default useStorage;
export enum CalendarViewType { Week, Month, } const [viewType, setViewType] = useStorage<CalendarViewType>( 'calendarViewType', CalendarViewType.Month, );
 
위와 같은 형태로 사용이 가능한데, return이 useState와 형태가 비슷하여 사용시에 어렵지 않게 하였다.
첫 번째 인자로는 localStorage 값이 저장될 key를 입력하고, 두 번째 값으로는 저장될 기본 값이 들어간다. 세 번째 값으로는 빈 값을 set 하게 될 경우 빈 값으로 지정할 것인지, localStorage에서 삭제할 것인지를 boolean으로 받는다.
 
이 Custom Hook을 사용 후 set을 하게 될 경우 Window의 Custom Event를 dispatch하게 되고,
이 Custom Event를 아래 Hook으로 수신할 수 있다.
 
import { useEffect } from 'react'; const useStorageListener = (storageKey: string, callback: (e: StorageEvent) => void) => { const handler = (e: StorageEvent) => { if (e.key === storageKey) { callback(e); } }; useEffect(() => { window.addEventListener('storage', handler); return () => { window.removeEventListener('storage', handler); }; }, []); }; export default useStorageListener;
const storageEventListener = useStorageListener('localstorage-key', () => { // 지정한 localStorage 값이 변경될 경우 실행할 callback });
 
+) 만들고 나서 코드를 다시 살펴보니, useStorage 훅의 defaultValue와 removable는 Object로 nullable하게 받아 처리해도 괜찮았을 것 같다.