react-hook-form-storage
Version:
A TypeScript library for React Hook Form persist functionality
1 lines • 11.3 kB
Source Map (JSON)
{"version":3,"file":"index.mjs","sources":["../src/utils.ts","../src/use-react-hook-form-storage.ts"],"sourcesContent":["import { FieldValues, Path } from 'react-hook-form';\nimport { Serializer, UseFormStorageOptions } from './types';\n\n/**\n * Filters the fields of an object based on included and excluded field lists.\n * @param values The object to filter.\n * @param included An optional list of fields to include.\n * @param excluded An optional list of fields to exclude.\n * @returns A new object with only the included or non-excluded fields.\n */\nexport const filterIncludedOrExcludedFields = (\n values: FieldValues,\n included?: string[],\n excluded?: string[]\n): Partial<FieldValues> => {\n return Object.entries(values).reduce((acc, [field, value]) => {\n // If included is defined, only include those fields\n if (included && !included.includes(field)) return acc;\n // If excluded is defined, skip those fields\n if (excluded && excluded.includes(field)) return acc;\n return {\n ...acc,\n [field]: value,\n };\n }, {});\n};\n\n/**\n * Debounces a function call.\n * @param cb The function to debounce.\n * @param delay The delay in milliseconds.\n * @returns A debounced version of the function.\n */\nexport const debouncer = <T extends (...args: any[]) => any>(\n cb: T,\n delay: number\n) => {\n let timeout: ReturnType<typeof setTimeout> | null = null;\n\n return ((...args: Parameters<T>) => {\n if (timeout) clearTimeout(timeout);\n timeout = setTimeout(() => cb(...args), delay);\n }) as T;\n};\n\n/**\n * Transforms the field values of an object using the provided serializer.\n * @param values The object containing field values to transform.\n * @param serializer An optional serializer object mapping field paths to serialization functions.\n * @param deserialize If true, applies deserialization instead of serialization.\n * @returns A new object with transformed field values.\n */\nexport const transformValues = <T extends FieldValues>(\n values: T,\n serializer: Record<string, Serializer<any, any>> = {},\n deserialize = false\n): Partial<T> => {\n const entries = Object.entries(values) as [Path<T>, any][];\n const serializerRecord = serializer as\n | Record<string, Serializer<T, any>>\n | undefined;\n\n return entries.reduce((acc, [field, value]) => {\n const fieldSerializer = serializerRecord?.[field];\n\n if (!fieldSerializer) return { ...acc, [field]: value };\n\n const transformFn = deserialize\n ? fieldSerializer.deserialize\n : fieldSerializer.serialize;\n\n if (!transformFn) return acc;\n\n const transformedValue = transformFn(value);\n return { ...acc, [field]: transformedValue };\n }, {});\n};\n","import type { FieldValues, Path, UseFormReturn } from 'react-hook-form';\nimport { useCallback, useEffect, useState } from 'react';\nimport {\n debouncer,\n filterIncludedOrExcludedFields,\n transformValues,\n} from './utils';\nimport { UseFormStorageOptions } from './types';\n\n/**\n * A React hook that provides automatic storage synchronization for react-hook-form.\n * Saves form data to storage (localStorage by default) and restores it on component mount.\n *\n * @template T - The form values type extending FieldValues\n * @param {string} key - Unique storage key for the form data\n * @param {UseFormReturn<T>} form - The react-hook-form instance\n * @param {UseFormStorageOptions<T>} options - Configuration options for storage behavior\n * @param {Storage} [options.storage=localStorage] - Storage implementation to use\n * @param {Path<T>[]} [options.included] - Fields to include in storage (whitelist)\n * @param {Path<T>[]} [options.excluded] - Fields to exclude from storage (blacklist)\n * @param {(values: Partial<T>) => void} [options.onRestore] - Callback when data is restored from storage\n * @param {(values: Partial<T>) => void} [options.onSave] - Callback when data is saved to storage\n * @param {number} [options.debounce] - Debounce delay in milliseconds for auto-save\n * @param {boolean} [options.dirty] - Whether to mark fields as dirty when restoring\n * @param {boolean} [options.touched] - Whether to mark fields as touched when restoring\n * @param {boolean} [options.validate] - Whether to validate fields when restoring\n * @param {Record<string, any>} [options.serializer] - Custom serialization functions for specific fields\n * @param {boolean} [options.autoSave=true] - Whether to automatically save changes\n *\n * @returns {Object} Hook return object\n * @returns {boolean} returns.isRestored - Whether data has been restored from storage\n * @returns {() => Promise<void>} returns.save - Manual save function to store current form values\n * @returns {() => Promise<void>} returns.clear - Function to clear stored data\n *\n * @example\n * ```tsx\n * const form = useForm<FormData>();\n * const { isRestored, save, clear } = useFormStorage('my-form', form, {\n * debounce: 500,\n * excluded: ['password'],\n * onRestore: (data) => console.log('Data restored:', data)\n * });\n *\n * // Manual operations\n * await save(); // Save current form state\n * await clear(); // Clear stored data\n * ```\n */\nexport const useFormStorage = <T extends FieldValues>(\n key: string,\n form: UseFormReturn<T>,\n {\n storage = localStorage,\n included,\n excluded,\n onRestore,\n onSave,\n debounce,\n dirty,\n touched,\n validate,\n serializer = {},\n autoSave = true,\n }: UseFormStorageOptions<T> = {}\n) => {\n const [isRestored, setIsRestored] = useState(false);\n\n const { setValue, watch } = form;\n\n const setStorageValue = useCallback(\n (key: string, value: string) => storage.setItem(key, value),\n [storage]\n );\n\n const getStorageValue = useCallback(\n (key: string) => {\n const value = storage.getItem(key);\n return value;\n },\n [storage]\n );\n\n const clearStorage = useCallback(\n (key: string) => storage.removeItem(key),\n [storage]\n );\n\n const saveToStorage = useCallback(\n async (values: Record<string, any>) => {\n // Filter out fields from the values to store\n const valuesToStore = filterIncludedOrExcludedFields(\n values,\n included,\n excluded\n );\n\n // Apply serialization if serializers are provided\n const serializedValues = transformValues(\n valuesToStore,\n // TODO: Fix type casting here\n serializer as any\n );\n\n await setStorageValue(key, JSON.stringify(serializedValues));\n // Call onUpdate callback if provided\n onSave?.(valuesToStore);\n },\n [key, included, excluded, onSave, serializer, setStorageValue]\n );\n\n // Load initial values from storage if available\n useEffect(() => {\n const restoreDataFromStorage = async () => {\n try {\n const storedValue = await getStorageValue(key);\n if (storedValue) {\n const parsedValue = JSON.parse(storedValue) as FieldValues;\n\n const valuesToRestore = filterIncludedOrExcludedFields(\n parsedValue,\n included,\n excluded\n );\n\n const deserializedValues = transformValues(\n valuesToRestore,\n // TODO: Fix type casting here\n serializer as any,\n true\n );\n\n // Set the values in the form\n Object.entries(deserializedValues).forEach(([field, value]) => {\n setValue(field as Path<T>, value, {\n shouldDirty: dirty,\n shouldTouch: touched,\n shouldValidate: validate,\n });\n });\n setIsRestored(true);\n // Call onDataRestore callback if provided\n onRestore?.(valuesToRestore);\n }\n } catch (error) {\n console.error('Failed to restore data from storage:', error);\n }\n };\n restoreDataFromStorage();\n }, []);\n\n // Watch for changes in form values and update storage\n useEffect(() => {\n // Cancel if autoSave is disabled\n if (!autoSave) return;\n\n const subscription = debounce\n ? watch(debouncer(saveToStorage, debounce))\n : watch(saveToStorage);\n\n return () => subscription.unsubscribe();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [watch, debounce]);\n\n return {\n isRestored,\n save: async () => saveToStorage(form.getValues()),\n clear: async () => clearStorage(key),\n };\n};\n"],"names":["filterIncludedOrExcludedFields","values","included","excluded","Object","entries","reduce","acc","field","value","includes","transformValues","serializer","deserialize","serializerRecord","fieldSerializer","transformFn","serialize","transformedValue","useFormStorage","key","form","storage","localStorage","onRestore","onSave","debounce","dirty","touched","validate","autoSave","isRestored","setIsRestored","useState","setValue","watch","setStorageValue","useCallback","setItem","getStorageValue","getItem","clearStorage","removeItem","saveToStorage","async","valuesToStore","serializedValues","JSON","stringify","useEffect","storedValue","parsedValue","parse","valuesToRestore","deserializedValues","forEach","shouldDirty","shouldTouch","shouldValidate","error","console","restoreDataFromStorage","subscription","cb","delay","timeout","args","clearTimeout","setTimeout","debouncer","unsubscribe","save","getValues","clear"],"mappings":"iEAUO,MAAMA,EAAiC,CAC5CC,EACAC,EACAC,IAEOC,OAAOC,QAAQJ,GAAQK,OAAO,CAACC,GAAMC,EAAOC,KAE7CP,IAAaA,EAASQ,SAASF,IAE/BL,GAAYA,EAASO,SAASF,GAFgBD,EAG3C,IACFA,EACHC,CAACA,GAAQC,GAEV,CAAA,GA4BQE,EAAkB,CAC7BV,EACAW,EAAmD,CAAA,EACnDC,GAAc,KAEd,MAAMR,EAAUD,OAAOC,QAAQJ,GACzBa,EAAmBF,EAIzB,OAAOP,EAAQC,OAAO,CAACC,GAAMC,EAAOC,MAClC,MAAMM,EAAkBD,eAAAA,EAAmBN,GAE3C,IAAKO,EAAiB,MAAO,IAAKR,EAAKC,CAACA,GAAQC,GAEhD,MAAMO,EAAcH,EAChBE,EAAgBF,YAChBE,EAAgBE,UAEpB,IAAKD,EAAa,OAAOT,EAEzB,MAAMW,EAAmBF,EAAYP,GACrC,MAAO,IAAKF,EAAKC,CAACA,GAAQU,IACzB,CAAA,IC3BQC,EAAiB,CAC5BC,EACAC,GAEEC,UAAUC,aACVrB,WACAC,WACAqB,YACAC,SACAC,WACAC,QACAC,UACAC,WACAjB,aAAa,CAAA,EACbkB,YAAW,GACiB,MAE9B,MAAOC,EAAYC,GAAiBC,GAAS,IAEvCC,SAAEA,EAAQC,MAAEA,GAAUd,EAEtBe,EAAkBC,EACtB,CAACjB,EAAaX,IAAkBa,EAAQgB,QAAQlB,EAAKX,GACrD,CAACa,IAGGiB,EAAkBF,EACrBjB,GACeE,EAAQkB,QAAQpB,GAGhC,CAACE,IAGGmB,EAAeJ,EAClBjB,GAAgBE,EAAQoB,WAAWtB,GACpC,CAACE,IAGGqB,EAAgBN,EACpBO,MAAO3C,IAEL,MAAM4C,EAAgB7C,EACpBC,EACAC,EACAC,GAII2C,EAAmBnC,EACvBkC,EAEAjC,SAGIwB,EAAgBhB,EAAK2B,KAAKC,UAAUF,IAE1CrB,SAAAA,EAASoB,IAEX,CAACzB,EAAKlB,EAAUC,EAAUsB,EAAQb,EAAYwB,IAwDhD,OApDAa,EAAU,KACuBL,WAC7B,IACE,MAAMM,QAAoBX,EAAgBnB,GAC1C,GAAI8B,EAAa,CACf,MAAMC,EAAcJ,KAAKK,MAAMF,GAEzBG,EAAkBrD,EACtBmD,EACAjD,EACAC,GAGImD,EAAqB3C,EACzB0C,EAEAzC,GACA,GAIFR,OAAOC,QAAQiD,GAAoBC,QAAQ,EAAE/C,EAAOC,MAClDyB,EAAS1B,EAAkBC,EAAO,CAChC+C,YAAa7B,EACb8B,YAAa7B,EACb8B,eAAgB7B,MAGpBG,GAAc,GAEdR,SAAAA,EAAY6B,EACd,CACF,CAAE,MAAOM,GACPC,QAAQD,MAAM,uCAAwCA,EACxD,GAEFE,IACC,IAGHZ,EAAU,KAER,IAAKnB,EAAU,OAEf,MAAMgC,EACF3B,EADiBT,ED1HA,EACvBqC,EACAC,KAEA,IAAIC,EAAgD,KAEpD,MAAA,IAAYC,KACND,GAASE,aAAaF,GAC1BA,EAAUG,WAAW,IAAML,KAAMG,GAAOF,EACzC,GCkHWK,CAAU1B,EAAejB,GACzBiB,GAEV,MAAO,IAAMmB,EAAaQ,eAEzB,CAACnC,EAAOT,IAEJ,CACLK,aACAwC,KAAM3B,SAAYD,EAActB,EAAKmD,aACrCC,MAAO7B,SAAYH,EAAarB"}