hooks.ts 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. import { useState, useEffect, useCallback } from 'react';
  2. /**
  3. * Custom hook for persisting form state to localStorage
  4. * @param key - Unique key for localStorage
  5. * @param initialValue - Initial value for the state
  6. * @returns [state, setState, clearState] tuple
  7. */
  8. export function useLocalStorage<T>(
  9. key: string,
  10. initialValue: T
  11. ): [T, (value: T | ((prevValue: T) => T)) => void, () => void] {
  12. // Always start with initialValue to prevent hydration mismatch
  13. // Load from localStorage only after client-side hydration
  14. const [storedValue, setStoredValue] = useState<T>(initialValue);
  15. // Load value from localStorage after component mounts (client-side only)
  16. useEffect(() => {
  17. const loadValue = () => {
  18. try {
  19. const item = window.localStorage.getItem(key);
  20. if (item) {
  21. setStoredValue(JSON.parse(item));
  22. }
  23. } catch (error) {
  24. console.warn(`Error loading localStorage key "${key}":`, error);
  25. }
  26. };
  27. // Use setTimeout to avoid calling setState synchronously during render
  28. const timeoutId = setTimeout(loadValue, 0);
  29. return () => clearTimeout(timeoutId);
  30. }, [key]);
  31. // Return a wrapped version of useState's setter function that ...
  32. // ... persists the new value to localStorage.
  33. const setValue = useCallback(
  34. (value: T | ((prevValue: T) => T)) => {
  35. try {
  36. // Allow value to be a function so we have same API as useState
  37. const valueToStore =
  38. value instanceof Function ? value(storedValue) : value;
  39. // Save state
  40. setStoredValue(valueToStore);
  41. // Save to local storage
  42. if (typeof window !== 'undefined') {
  43. window.localStorage.setItem(key, JSON.stringify(valueToStore));
  44. }
  45. } catch (error) {
  46. // A more advanced implementation would handle the error case
  47. console.error(`Error saving localStorage key "${key}":`, error);
  48. }
  49. },
  50. [key, storedValue]
  51. );
  52. // Function to clear the stored value
  53. const clearValue = useCallback(() => {
  54. try {
  55. setStoredValue(initialValue);
  56. if (typeof window !== 'undefined') {
  57. window.localStorage.removeItem(key);
  58. }
  59. } catch (error) {
  60. console.error(`Error clearing localStorage key "${key}":`, error);
  61. }
  62. }, [key, initialValue]);
  63. return [storedValue, setValue, clearValue];
  64. }
  65. /**
  66. * Hook for auto-saving form state with debouncing
  67. * @param key - Unique key for localStorage
  68. * @param value - Current form value
  69. * @param delay - Debounce delay in milliseconds (default: 500ms)
  70. */
  71. export function useAutoSave<T>(key: string, value: T, delay = 500) {
  72. useEffect(() => {
  73. const timeoutId = setTimeout(() => {
  74. if (typeof window !== 'undefined') {
  75. try {
  76. window.localStorage.setItem(key, JSON.stringify(value));
  77. } catch (error) {
  78. console.error(`Error auto-saving localStorage key "${key}":`, error);
  79. }
  80. }
  81. }, delay);
  82. return () => clearTimeout(timeoutId);
  83. }, [key, value, delay]);
  84. }