All files / src/components/SearchPanel/hooks useContext.ts

100% Statements 119/119
100% Branches 38/38
100% Functions 10/10
100% Lines 119/119

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 1721x 1x 1x 1x 1x   1x   1x                 1x 1x 2x 2x 2x 2x   1x                       1x 1x 3x 3x 3x 3x 3x 3x 3x 3x     3x 1x 1x   3x 3x   1x     1x 1x 12x 12x 12x           12x   12x 12x   12x 12x 12x 12x 11x 12x   12x 2x 2x 2x 1x 1x 1x 2x   3x 3x 12x 1x 1x 1x 1x 1x 12x     12x 3x 3x 3x 3x 1x 1x 1x 3x   12x 2x 2x 1x 1x 1x 1x 1x 1x 2x   12x 12x 12x 12x 12x 12x   1x       1x 1x 5x 5x 5x 5x 5x 4x 2x 2x 2x   2x 4x   5x   5x 5x 5x 5x 5x 3x 5x 5x   5x 5x 5x 5x   5x   5x 1x 1x   5x 5x  
import { AppContext } from '@/context';
import { cstorage } from '@/utils/storage';
import { use } from 'react';
import { Logger } from '@/utils/Logger';
import { getErrorMessage } from '@/helpers/exceptions';
 
const logger = new Logger('useContext');
 
/**
 * React v19's use() hook can work with Context directly
 *
 * Benefits over useContext:
 * - No need for separate error handling wrapper
 * - Works with Suspense boundaries
 * - Cleaner, more direct API
 * - Future-proof for React's direction
 * @source
 */
export function useAppContext() {
  // No need for separate useContext - use() handles it
  // The context provider's error handling is sufficient
  return use(AppContext);
}
 
/**
 * Promise-based Chrome storage with use() hook
 *
 * This demonstrates how React v19's use() hook can handle promises directly,
 * eliminating the need for useEffect + useState patterns for async data.
 *
 * Benefits:
 * - Direct promise handling without useEffect
 * - Automatic Suspense integration
 * - Cleaner error boundaries
 * - Reduces boilerplate code
 * @source
 */
export function useChromeStorage<T>(key: string, defaultValue: T) {
  // The use() hook can handle promises directly
  // This will suspend the component until the promise resolves
  const storedValue = use(
    (async () => {
      const data = await cstorage.session.get([key]);
      // chrome.storage returns untyped data; narrow to the caller's generic T.
      return data[key] !== undefined ? (data[key] as T) : defaultValue;
    })(),
  );
 
  const setValue = (value: T) => {
    cstorage.session.set({ [key]: value });
  };
 
  return [storedValue, setValue] as const;
}
 
/**
 * Enhanced version with error handling and type safety
 * @source
 */
export function useChromeStorageEnhanced<T>(
  key: string,
  defaultValue: T,
  options?: {
    storage?: 'session' | 'local';
    serializer?: {
      serialize: (value: T) => string;
      deserialize: (value: string) => T;
    };
  },
) {
  const storage = options?.storage === 'local' ? cstorage.local : cstorage.session;
  const { serializer } = options || {};
 
  const storedValue = use(
    (async () => {
      try {
        const data = await storage.get([key]);
        const rawValue = data[key];
        if (rawValue === undefined) return defaultValue;
 
        if (serializer) {
          try {
            return serializer.deserialize(rawValue);
          } catch (error) {
            logger.warn(`Failed to deserialize ${key}: ${getErrorMessage(error)}`, { error });
            return defaultValue;
          }
        }
 
        // chrome.storage returns untyped data; narrow to the caller's generic T.
        return rawValue as T;
      } catch (error) {
        logger.error(`Failed to load ${key} from Chrome storage: ${getErrorMessage(error)}`, {
          error,
        });
        return defaultValue;
      }
    })(),
  );
 
  const setValue = async (value: T) => {
    try {
      const valueToStore = serializer ? serializer.serialize(value) : value;
      await storage.set({ [key]: valueToStore });
    } catch (error) {
      logger.error(`Failed to save ${key} to Chrome storage: ${getErrorMessage(error)}`, { error });
      throw error;
    }
  };
 
  const removeValue = async () => {
    try {
      await storage.remove([key]);
    } catch (error) {
      logger.error(`Failed to remove ${key} from Chrome storage: ${getErrorMessage(error)}`, {
        error,
      });
      throw error;
    }
  };
 
  return {
    value: storedValue,
    setValue,
    removeValue,
  };
}
 
/**
 * Reactive Chrome storage hook that watches for changes
 * Uses React v19's use() hook with a custom promise that updates on storage changes
 * @source
 */
export function useReactiveChromeStorage<T>(key: string, defaultValue: T) {
  // Create a promise that resolves and updates when storage changes
  const createStoragePromise = () => {
    return new Promise<T>((resolve) => {
      // Listen for changes
      const listener = (changes: { [key: string]: chrome.storage.StorageChange }) => {
        if (changes[key]) {
          // chrome.storage change values are untyped; narrow to the caller's generic T.
          resolve(
            changes[key].newValue !== undefined ? (changes[key].newValue as T) : defaultValue,
          );
        }
      };
 
      cstorage.onChanged.addListener(listener);
 
      // Initial load. The executor must stay synchronous (no async Promise
      // executor), so the await runs inside a voided async IIFE; resolve() is
      // idempotent, so the first value (initial load or an early change) wins.
      void (async () => {
        const data = await cstorage.session.get([key]);
        // chrome.storage returns untyped data; narrow to the caller's generic T.
        resolve(data[key] !== undefined ? (data[key] as T) : defaultValue);
      })();
 
      // Cleanup is handled by React's concurrent features
      // This is a simplified example - in practice you'd want proper cleanup
    });
  };
 
  const value = use(createStoragePromise());
 
  const setValue = (newValue: T) => {
    cstorage.session.set({ [key]: newValue });
  };
 
  return [value, setValue] as const;
}