Hits

import { Storage, safeLocalStorage } from "@/utils";
import { SetStateAction, useCallback, useState } from "react";
 
type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
type ToObject<T> = T extends unknown[] | Record<string, unknown> ? T : never;
 
type Serializable<T> = T extends string | number | boolean ? ToPrimitive<T> : ToObject<T>;
 
interface StorageStateOptions<T> {
  storage?: Storage;
  defaultValue?: Serializable<T>;
}
 
interface StorageStateOptionsWithDefaultValue<T> extends StorageStateOptions<T> {
  defaultValue: Serializable<T>;
}
 
export function useStorageState<T>(
  key: string,
  { storage, defaultValue }: StorageStateOptionsWithDefaultValue<T>
): readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void, () => void];
 
export function useStorageState<T>(
  key: string,
  { storage = safeLocalStorage, defaultValue }: StorageStateOptions<T> = {}
): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void] {
  const getValue = useCallback(<T,>() => {
    const data = storage.get(key);
 
    if (data == null) {
      return defaultValue;
    }
 
    try {
      const result = JSON.parse(data);
 
      if (result == null) {
        return defaultValue;
      }
 
      return result as T;
    } catch {
      return defaultValue;
    }
  }, [defaultValue, key, storage]);
 
  const [state, setState] = useState<Serializable<T> | undefined>(getValue);
 
  const set = useCallback(
    (value: SetStateAction<Serializable<T> | undefined>) => {
      setState((curr) => {
        const nextValue = typeof value === "function" ? value(curr) : value;
 
        if (nextValue == null) {
          storage.remove(key);
        } else {
          storage.set(key, JSON.stringify(nextValue));
        }
 
        return nextValue;
      });
    },
    [key, storage]
  );
 
  const refresh = useCallback(() => {
    setState(getValue() ?? defaultValue);
  }, [defaultValue, getValue]);
 
  return [state, set, refresh] as const;
}
 
export default useStorageState;
import { Storage, safeLocalStorage } from "@/utils";
import { SetStateAction, useCallback, useState } from "react";
 
type ToPrimitive<T> = T extends string ? string : T extends number ? number : T extends boolean ? boolean : never;
type ToObject<T> = T extends unknown[] | Record<string, unknown> ? T : never;
 
type Serializable<T> = T extends string | number | boolean ? ToPrimitive<T> : ToObject<T>;
 
interface StorageStateOptions<T> {
  storage?: Storage;
  defaultValue?: Serializable<T>;
}
 
interface StorageStateOptionsWithDefaultValue<T> extends StorageStateOptions<T> {
  defaultValue: Serializable<T>;
}
 
export function useStorageState<T>(
  key: string,
  { storage, defaultValue }: StorageStateOptionsWithDefaultValue<T>
): readonly [Serializable<T>, (value: SetStateAction<Serializable<T>>) => void, () => void];
 
export function useStorageState<T>(
  key: string,
  { storage = safeLocalStorage, defaultValue }: StorageStateOptions<T> = {}
): readonly [Serializable<T> | undefined, (value: SetStateAction<Serializable<T> | undefined>) => void, () => void] {
  const getValue = useCallback(<T,>() => {
    const data = storage.get(key);
 
    if (data == null) {
      return defaultValue;
    }
 
    try {
      const result = JSON.parse(data);
 
      if (result == null) {
        return defaultValue;
      }
 
      return result as T;
    } catch {
      return defaultValue;
    }
  }, [defaultValue, key, storage]);
 
  const [state, setState] = useState<Serializable<T> | undefined>(getValue);
 
  const set = useCallback(
    (value: SetStateAction<Serializable<T> | undefined>) => {
      setState((curr) => {
        const nextValue = typeof value === "function" ? value(curr) : value;
 
        if (nextValue == null) {
          storage.remove(key);
        } else {
          storage.set(key, JSON.stringify(nextValue));
        }
 
        return nextValue;
      });
    },
    [key, storage]
  );
 
  const refresh = useCallback(() => {
    setState(getValue() ?? defaultValue);
  }, [defaultValue, getValue]);
 
  return [state, set, refresh] as const;
}
 
export default useStorageState;

Reference

https://github.com/toss/slash/blob/main/packages/react/react/src/hooks/useStorageState.ko.md