SCALE — Build Lab
ロジック · REACT HOOK

localStorage 永続化レイヤー

CATEGORYロジック TYPEReact Hook EFFORT30〜60分 DIFFICULTY
PRIMARY CODE
typescript · lib/finance-store.ts
const STORE_PREFIX = 'scale-finance-v1-';

export function useFinanceData<T>(key: string, defaultValue: T): [T, (v: T | ((p: T) => T)) => void, () => void] {
  const [data, setData] = useState<T>(defaultValue);

  useEffect(() => {
    if (typeof window === 'undefined') return;
    const raw = localStorage.getItem(STORE_PREFIX + key);
    if (raw) try { setData(JSON.parse(raw)); } catch {}
  }, [key]);

  const update = useCallback((v: T | ((p: T) => T)) => {
    setData(prev => {
      const next = typeof v === 'function' ? (v as any)(prev) : v;
      localStorage.setItem(STORE_PREFIX + key, JSON.stringify(next));
      return next;
    });
  }, [key]);

  const reset = useCallback(() => {
    localStorage.removeItem(STORE_PREFIX + key);
    setData(defaultValue);
  }, [key, defaultValue]);

  return [data, update, reset];
}

// 配列向け
export function useFinanceArray<T>(key: string, defaultRows: T[]) {
  const [rows, setRows, reset] = useFinanceData<T[]>(key, defaultRows);
  return {
    rows, setRows, reset,
    updateRow: (i, patch) => setRows(prev => prev.map((r, j) => j === i ? { ...r, ...patch } : r)),
    addRow: (row) => setRows(prev => [...prev, row]),
    removeRow: (i) => setRows(prev => prev.filter((_, j) => j !== i)),
  };
}
USE CASES
  • 編集データの永続化(API無しで動く)
  • オフライン対応のテーブル編集
  • 1アカウント=1デバイス用途

useFinanceData / useFinanceArray(localStorage 永続化フック)

:LiTarget: 用途

React の useState 風 API で、自動で localStorage に保存・復元するフック。

  • key prefix <system>-v1- で名前空間分離
  • defaultValue 引数(初回・リセット時に使用)
  • reset() でデフォルト値に戻す
  • 配列向け便利フック useFinanceArray(addRow / removeRow / updateRow 付き)

:LiCode: コード骨格

const STORE_PREFIX = 'scale-finance-v1-';

export function useFinanceData<T>(key: string, defaultValue: T): [T, (v: T | ((p: T) => T)) => void, () => void] {
  const [data, setData] = useState<T>(defaultValue);

  useEffect(() => {
    if (typeof window === 'undefined') return;
    const raw = localStorage.getItem(STORE_PREFIX + key);
    if (raw) try { setData(JSON.parse(raw)); } catch {}
  }, [key]);

  const update = useCallback((v: T | ((p: T) => T)) => {
    setData(prev => {
      const next = typeof v === 'function' ? (v as any)(prev) : v;
      localStorage.setItem(STORE_PREFIX + key, JSON.stringify(next));
      return next;
    });
  }, [key]);

  const reset = useCallback(() => {
    localStorage.removeItem(STORE_PREFIX + key);
    setData(defaultValue);
  }, [key, defaultValue]);

  return [data, update, reset];
}

// 配列向け
export function useFinanceArray<T>(key: string, defaultRows: T[]) {
  const [rows, setRows, reset] = useFinanceData<T[]>(key, defaultRows);
  return {
    rows, setRows, reset,
    updateRow: (i, patch) => setRows(prev => prev.map((r, j) => j === i ? { ...r, ...patch } : r)),
    addRow: (row) => setRows(prev => [...prev, row]),
    removeRow: (i) => setRows(prev => prev.filter((_, j) => j !== i)),
  };
}

:LiAlertCircle: 注意

  • localStorage はオリジン分離(ドメイン変更時にデータ消える ← Base ドメイン変更で経験済)
  • 多端末同期不要なら最強・必要なら KV API 化推奨