# useLocalStorage

## Introduction

이 훅을 함수형 컴포넌트에서 사용하여 특정 키에 대한 로컬 스토리지를 관리할 수 있습니다. 이 훅의 사용 목적으로는 다음과 같습니다.

1. **로컬 스토리지 관리의 추상화:**
   * `useLocalStorage` 훅은 로컬 스토리지와 관련된 복잡한 작업을 추상화하고 간소화합니다. 이를 통해 컴포넌트에서 간편하게 로컬 스토리지를 다룰 수 있습니다.
2. **리액트 상태와의 통합:**
   * 훅은 `useState`를 사용하여 로컬 스토리지의 값을 리액트 상태로 관리합니다. 이는 컴포넌트의 상태를 업데이트하면서 로컬 스토리지도 동기화되어 사용자 인터페이스가 실시간으로 반응할 수 있습니다.
3. **캡슐화와 재사용성:**
   * 로컬 스토리지와 관련된 로직이 훅에 캡슐화되어 있기 때문에, 이 훅을 다른 컴포넌트에서 간단히 재사용할 수 있습니다. 이로써 중복 코드를 방지하고 일관된 로컬 스토리지 관리를 할 수 있습니다.
4. **이벤트 핸들링 및 상태 업데이트:**
   * `update` 함수를 사용하여 로컬 스토리지 값이 다른 탭이나 창에서 변경되었을 때 실시간으로 상태를 업데이트할 수 있습니다. 이로써 다양한 탭 간에 데이터 동기화를 달성할 수 있습니다.
5. **서버 사이드 렌더링(SSR) 대응:**
   * 훅은 서버 환경에서 실행될 때 로컬 스토리지에 접근하는 문제를 방지합니다. SSR 환경에서 안전하게 사용할 수 있도록 구현되어 있습니다.
6. **옵션 설정으로 초기값 관리:**
   * 옵션을 통해 초기값을 로컬 스토리지에 저장할지 여부를 관리할 수 있습니다. 이는 특정 키에 대한 초기값을 설정하고, 로컬 스토리지에 해당 키의 값이 없을 때 초기값을 저장할 수 있습니다.

## Example

```tsx
const MyComponent = () => {
  const { read, save, remove, update } = useLocalStorage("myKey", "default-value", { initialSave: true });
​
  useEffect(() => {
    // 로컬 스토리지가 변경될 때 동작을 수행
    update();
  }, []);
​
  return (
    <div>
      <p>저장된 값: {read()}</p>
      <button onClick={() => save("new-value")}>로컬 스토리지에 저장</button>
      <button onClick={remove}>로컬 스토리지에서 삭제</button>
    </div>
  );
};
```

이 훅은 React 컴포넌트에서 로컬 스토리지 작업을 편리하게 관리할 수 있도록 도와줍니다.

## Hook

```tsx
import type { Dispatch, SetStateAction } from "react";
import { useCallback, useEffect, useState } from "react";
​
type SetValue<T> = Dispatch<SetStateAction<T>>;
​
interface Options {
  initialSave: boolean;
}
​
const isServer = typeof window === "undefined";
​
/**
 * 이 훅은 로컬스토리지를 관리하는 커스텀 훅 입니다.
 * 기본적으로 로컬스토리지의 값을 읽기, 저장, 삭제, 업데이트 등의 기능을 제공하고 있습니다.
 * @param {string} key - 로컬 스토리지의 Key
 * @param {T} initialValue - 로컬 스토리지의 초깃값
 * @param {Options} options - 훅에서 쓰이는 옵션 값
 * @returns {()=>{}} read(읽기), save(저장), remove(삭제), update(업데이트 마운트)
 */
​
/**
 *
 * @param options
 * @description options의 기능에 대한 설명
 * initialSave : 스토리지에 저장된 값이 없을 경우, 초기값을 저장할 것인지 말 것인지 type : boolean
 */
export default function useLocalStorage<T>(key: string, initialValue: T | (() => T), options: Options = { initialSave: false }) {
  const [storedValue, setStoredValue] = useState<T>(initialValue);
​
  const isStorageEvent = (event: StorageEvent | CustomEvent): event is StorageEvent => {
    return "key" in event;
  };
​
  /** 로컬 스토리지 읽기 */
  const readValueFromLocalStorage = (): T => {
    const initialValueToUse = initialValue instanceof Function ? initialValue() : initialValue;
​
    // SSR에서 발생할 수 있는 오류 방지하기 위해 설정. window 객체가 존재하지 않을때, localStorage에 접근 하는 것을 방지
    if (isServer) {
      return initialValueToUse;
    }
​
    try {
      const rawData = window.localStorage.getItem(key) as T;
​
      /** 값이 없을 경우 */
      if (typeof rawData === "object" && rawData === null && options.initialSave) {
        save(initialValueToUse);
        return initialValueToUse;
      }
​
      return rawData ? rawData : initialValueToUse;
    } catch {
      console.warn(`로컬 스토리지를 가져오는 것에 실패하였습니다. 키 : "${key}", 값 : error  `);
      return initialValueToUse;
    }
  };
  // read함수 메모이제이션
  const read = useCallback<() => T>(() => {
    return readValueFromLocalStorage();
  }, []);
​
  useEffect(() => {
    setStoredValue(read());
  }, [key]);
​
  /** 로컬 스토리지 읽기 */
​
  /** 로컬 스토리지 저장 */
  const saveValueFromLocalStorage = (value: T) => {
    if (isServer) {
      console.warn("브라우저 환경에서만 사용 가능합니다. 서버환경에서 사용이 불가능합니다.");
      return undefined;
    }
​
    try {
      setStoredValue(value);
      window.localStorage.setItem(key, JSON.stringify(value));
      return value;
    } catch (error) {
      if (error instanceof DOMException && error.name === "QuotaExceededError") {
        console.error("실패 : 로컬 스토리지 용량이 부족합니다.");
      } else {
        console.warn(`로컬 스토리지에 값을 저장하는 데 실패했습니다. 키: "${key}"`);
      }
    }
  };
​
  const save: SetValue<T> = useCallback(async (value: T) => {
    return await saveValueFromLocalStorage(value);
  }, []);
  /** 로컬 스토리지 저장 */
​
  /** 로컬 스토리지 변화에 따른 상태관리 감지 */
  const update = useCallback(
    (event: StorageEvent | CustomEvent) => {
      if (isStorageEvent(event) && event.key !== key) return;
      setStoredValue(read());
    },
    [key, read]
  );
  /** 로컬 스토리지 변화에 따른 상태관리 감지 */
​
  /** 로컬 스토리지 삭제 */
  const remove = () => {
    if (isServer) {
      console.warn("현재 브라우저 환경이 아닙니다.");
      return key;
    }
​
    try {
      window.localStorage.removeItem(key);
      setStoredValue(undefined);
    } catch (error) {
      console.warn(`로컬 스토리지에서 항목을 삭제하는 데 실패했습니다. 키: "${key}"`);
      return;
    }
  };
​
  return { read, save, remove, update };
}
```

이 훅은 React 애플리케이션에서 로컬 스토리지를 관리하기 위한 커스텀 React 훅인 `useLocalStorage`을 정의합니다. 이 훅은 로컬 스토리지에서 값 읽기, 저장하기, 업데이트하기, 삭제하기 등의 기능을 제공합니다.

주요 구성 요소를 살펴보겠습니다:

1. **타입 정의:**
   * `SetValue<T>`: `T` 타입의 상태를 설정하는 함수를 나타내는 타입.
   * `Options`: 훅에 대한 구성 옵션을 정의하는 인터페이스.
2. **훅 구현:**
   * 훅은 세 개의 매개변수를 받습니다:
     * `key`: 로컬 스토리지에 데이터를 저장할 키를 나타내는 문자열.
     * `initialValue`: 로컬 스토리지에 데이터가 없을 때 사용할 초기값.
     * `options`: 훅의 구성 옵션을 나타내는 선택적 매개변수.
   * 이 훅은 로컬 스토리지에서 데이터를 읽기, 저장하기, 업데이트하기 및 삭제하기 위한 메서드를 가진 객체를 반환합니다.
3. **기능:**
   * 훅은 서버 환경(SSR)에서 실행 중인지 확인하여 `window` 및 로컬 스토리지에 액세스하는 데 잠재적인 문제를 방지합니다.
   * `useState` 훅을 사용하여 저장된 값의 상태를 관리합니다.
   * `readValueFromLocalStorage` 함수는 로컬 스토리지에서 값을 읽는 역할을 합니다. 값이 없고 `initialSave` 옵션이 `true`로 설정된 경우 초기값을 로컬 스토리지에 저장합니다.
   * `read` 함수는 `readValueFromLocalStorage`의 메모이제이션된 버전입니다.
   * `useEffect` 훅은 `key`가 변경될 때마다 상태를 업데이트합니다.
   * `saveValueFromLocalStorage` 함수는 제공된 값을 로컬 스토리지에 저장하고 상태를 업데이트합니다.
   * `save` 함수는 `saveValueFromLocalStorage`의 메모이제이션된 버전입니다.
   * `update` 함수는 로컬 스토리지의 변경(다른 탭이나 창에서 발생)을 처리하고 상태를 업데이트합니다.
   * `remove` 함수는 로컬 스토리지에서 항목을 삭제하고 상태를 `undefined`로 설정합니다.
