SCALE — Build Lab
開発パターン · REACT PATTERN

自動保存パターン

CATEGORY開発パターン TYPEReact Pattern EFFORT60〜180分 DIFFICULTY
PRIMARY CODE
tsx
import { useEffect, useRef, useState } from 'react';

// 自動保存フック(debounce + ステータス管理)
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error';

export function useAutoSave<T>(
  value: T,
  save: (v: T) => Promise<void>,
  delay = 1000,
) {
  const [status, setStatus] = useState<SaveStatus>('idle');
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const initial = useRef(true);

  useEffect(() => {
    if (initial.current) { initial.current = false; return; }
    if (timer.current) clearTimeout(timer.current);
    setStatus('idle');
    timer.current = setTimeout(async () => {
      setStatus('saving');
      try {
        await save(value);
        setStatus('saved');
        setTimeout(() => setStatus('idle'), 1500);
      } catch {
        setStatus('error');
      }
    }, delay);
    return () => { if (timer.current) clearTimeout(timer.current); };
  }, [value, delay, save]);

  return status;
}

// 使い方:
// const status = useAutoSave(content, (c) => api.saveDoc(docId, c));
// <span>{status === 'saving' ? '保存中…' : status === 'saved' ? '保存済' : ''}</span>
前提条件
Tailwind CSS v4TypeScript 5
USE CASES
  • ドキュメント編集
  • ノート

自動保存パターン

:LiTarget: 用途

フォーム入力を debounce してバックエンドに自動保存するパターン。

:LiSparkle: 特徴

  • debounce
  • 保存ステータス表示
  • エラーリトライ

:LiCode: コード(コピペ用)

import { useEffect, useRef, useState } from 'react';

// 自動保存フック(debounce + ステータス管理)
type SaveStatus = 'idle' | 'saving' | 'saved' | 'error';

export function useAutoSave<T>(
  value: T,
  save: (v: T) => Promise<void>,
  delay = 1000,
) {
  const [status, setStatus] = useState<SaveStatus>('idle');
  const timer = useRef<ReturnType<typeof setTimeout> | null>(null);
  const initial = useRef(true);

  useEffect(() => {
    if (initial.current) { initial.current = false; return; }
    if (timer.current) clearTimeout(timer.current);
    setStatus('idle');
    timer.current = setTimeout(async () => {
      setStatus('saving');
      try {
        await save(value);
        setStatus('saved');
        setTimeout(() => setStatus('idle'), 1500);
      } catch {
        setStatus('error');
      }
    }, delay);
    return () => { if (timer.current) clearTimeout(timer.current); };
  }, [value, delay, save]);

  return status;
}

// 使い方:
// const status = useAutoSave(content, (c) => api.saveDoc(docId, c));
// <span>{status === 'saving' ? '保存中…' : status === 'saved' ? '保存済' : ''}</span>

:LiHandPointer: 使い方

対象プロジェクトに該当ファイルをコピーして、props を流し込むだけ。

:LiAlertCircle: 注意事項

  • 依存パッケージを忘れず追加