Files
metabuilder/hooks/useLocalStorage.ts
johndoe6345789 5aabff44cd refactor(fakemui): flatten QML components directory structure and update documentation
Directory Restructuring:
- qml/qml-components/qml-components/* → qml/components/ (flattens nesting)
- All 104 QML files moved with git history preserved
- Eliminates redundant qml-components nesting

Documentation Updates:
- ARCHITECTURE.md: Updated qml/components references (2 locations)
- GETTING_STARTED.md: Updated qml/components path (1 location, end of file)
- README.md: Updated qml/components references (3 locations)
- CODE_REVIEW.md: Updated qml/components file paths (4 locations)
- docs/ARCHITECTURE.md: Complete refactor with qml/components paths

Verification:
-  No remaining qml-components/ references in documentation
-  All 104 QML files present in flattened structure
-  Directory structure verified (12 component categories)
-  First-class directory naming convention

Structure Post-Refactor:
qml/
├── components/
│   ├── atoms/ (16 files)
│   ├── core/ (11 files)
│   ├── data-display/ (10 files)
│   ├── feedback/ (11 files)
│   ├── form/ (19 files)
│   ├── lab/ (11 files)
│   ├── layout/ (12 files)
│   ├── navigation/ (12 files)
│   ├── surfaces/ (7 files)
│   ├── theming/ (4 files)
│   └── utils/ (13 files)
├── hybrid/
└── widgets/

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-01-23 19:54:21 +00:00

157 lines
4.4 KiB
TypeScript

/**
* useLocalStorage Hook
* Enhanced localStorage hook with versioning, serialization, and type safety
*
* Features:
* - Generic typing for any value type
* - Automatic JSON serialization/deserialization
* - Version-based persistence (prevents stale data conflicts)
* - Graceful fallback to initial value on parse errors
* - SSR-safe (checks for window availability)
* - Clear method for removing values
*
* @example
* const { value, setValue, clear } = useLocalStorage<User>(
* 'user-profile',
* { id: '', name: '', email: '' },
* { version: 1 }
* )
*
* // Use in component
* <TextField
* value={value.name}
* onChange={(e) => setValue({ ...value, name: e.target.value })}
* />
* <Button onClick={clear}>Clear Profile</Button>
*
* @example
* // With object/complex types
* interface AppSettings {
* theme: 'light' | 'dark'
* language: string
* notifications: boolean
* }
*
* const { value: settings, setValue: updateSettings } = useLocalStorage<AppSettings>(
* 'app-settings',
* { theme: 'light', language: 'en', notifications: true }
* )
*/
import { useState, useEffect, useCallback } from 'react'
export interface UseLocalStorageOptions {
/** Storage version for data migration - prevents conflicts with old data */
version?: number
/** Custom serializer function */
serializer?: (value: any) => string
/** Custom deserializer function */
deserializer?: (value: string) => any
/** Sync storage changes across browser tabs */
syncTabs?: boolean
}
export interface UseLocalStorageReturn<T> {
/** Current stored value */
value: T
/** Update stored value */
setValue: (value: T | ((prev: T) => T)) => void
/** Remove from storage and reset to initial value */
clear: () => void
}
export function useLocalStorage<T>(
key: string,
initialValue: T,
options: UseLocalStorageOptions = {}
): UseLocalStorageReturn<T> {
const {
version = 1,
serializer = (value) => JSON.stringify({ version, data: value }),
deserializer = (value) => {
try {
const parsed = JSON.parse(value)
// Handle both new format (with version) and old format (direct value)
if (parsed && typeof parsed === 'object' && 'version' in parsed && 'data' in parsed) {
return parsed.version === version ? parsed.data : initialValue
}
return parsed
} catch {
return initialValue
}
},
syncTabs = true,
} = options
const [storedValue, setStoredValue] = useState<T>(() => {
// SSR-safe check
if (typeof window === 'undefined') {
return initialValue
}
try {
const item = window.localStorage.getItem(key)
return item ? deserializer(item) : initialValue
} catch {
// Handle localStorage access errors (e.g., quota exceeded, private browsing)
return initialValue
}
})
// Update localStorage when state changes
const handleSetValue = useCallback(
(value: T | ((prev: T) => T)) => {
try {
const valueToStore = value instanceof Function ? value(storedValue) : value
setStoredValue(valueToStore)
if (typeof window !== 'undefined') {
window.localStorage.setItem(key, serializer(valueToStore))
}
} catch (error) {
// Handle quota exceeded and other storage errors
console.warn(`useLocalStorage: Failed to store value for key "${key}":`, error)
}
},
[key, serializer, storedValue]
)
// Handle storage changes in other tabs
useEffect(() => {
if (!syncTabs || typeof window === 'undefined') return
const handleStorageChange = (e: StorageEvent) => {
if (e.key === key && e.newValue) {
try {
setStoredValue(deserializer(e.newValue))
} catch {
// Handle deserialization errors
}
}
}
window.addEventListener('storage', handleStorageChange)
return () => {
window.removeEventListener('storage', handleStorageChange)
}
}, [key, deserializer, syncTabs])
// Clear storage
const handleClear = useCallback(() => {
try {
setStoredValue(initialValue)
if (typeof window !== 'undefined') {
window.localStorage.removeItem(key)
}
} catch (error) {
console.warn(`useLocalStorage: Failed to clear key "${key}":`, error)
}
}, [key, initialValue])
return {
value: storedValue,
setValue: handleSetValue,
clear: handleClear,
}
}