use-draft-cache.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. 'use client';
  2. import { useState, useRef, useCallback, useEffect } from 'react';
  3. interface UseDraftCacheOptions {
  4. key: string;
  5. debounceMs?: number;
  6. }
  7. interface UseDraftCacheReturn<T> {
  8. cachedValue: T | undefined;
  9. updateCache: (value: T) => void;
  10. clearCache: () => void;
  11. }
  12. export function useDraftCache<T>({
  13. key,
  14. debounceMs = 500,
  15. }: UseDraftCacheOptions): UseDraftCacheReturn<T> {
  16. const [cachedValue] = useState<T | undefined>(() => {
  17. if (typeof window === 'undefined') return undefined;
  18. try {
  19. const raw = localStorage.getItem(key);
  20. if (raw !== null) {
  21. return JSON.parse(raw) as T;
  22. }
  23. } catch {
  24. /* ignore parse errors */
  25. }
  26. return undefined;
  27. });
  28. const timerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
  29. const pendingValueRef = useRef<T | undefined>(undefined);
  30. const keyRef = useRef(key);
  31. useEffect(() => {
  32. keyRef.current = key;
  33. }, [key]);
  34. const flushPending = useCallback(() => {
  35. if (timerRef.current !== null) {
  36. clearTimeout(timerRef.current);
  37. timerRef.current = null;
  38. }
  39. if (pendingValueRef.current !== undefined) {
  40. try {
  41. localStorage.setItem(keyRef.current, JSON.stringify(pendingValueRef.current));
  42. } catch {
  43. /* ignore quota errors */
  44. }
  45. pendingValueRef.current = undefined;
  46. }
  47. }, []);
  48. const updateCache = useCallback(
  49. (value: T) => {
  50. pendingValueRef.current = value;
  51. if (timerRef.current !== null) {
  52. clearTimeout(timerRef.current);
  53. }
  54. timerRef.current = setTimeout(() => {
  55. timerRef.current = null;
  56. try {
  57. localStorage.setItem(keyRef.current, JSON.stringify(value));
  58. } catch {
  59. /* ignore quota errors */
  60. }
  61. pendingValueRef.current = undefined;
  62. }, debounceMs);
  63. },
  64. [debounceMs],
  65. );
  66. const clearCache = useCallback(() => {
  67. if (timerRef.current !== null) {
  68. clearTimeout(timerRef.current);
  69. timerRef.current = null;
  70. }
  71. pendingValueRef.current = undefined;
  72. try {
  73. localStorage.removeItem(keyRef.current);
  74. } catch {
  75. /* ignore */
  76. }
  77. }, []);
  78. // Flush pending write on unmount
  79. useEffect(() => {
  80. return () => {
  81. flushPending();
  82. };
  83. }, [flushPending]);
  84. return { cachedValue, updateCache, clearCache };
  85. }