Files
metabuilder/hooks/useAsync.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

167 lines
4.3 KiB
TypeScript

/**
* useAsync Hook
* Async function wrapper with loading, error, and data state management
*
* Features:
* - Wraps async functions with automatic loading/error/data handling
* - Generic typing for async function return types
* - Manual execution with execute() method
* - Reset functionality to clear data and errors
* - Automatic cleanup to prevent memory leaks
* - Dependency tracking for re-execution
*
* @example
* const fetchUser = async (id: string) => {
* const res = await fetch(`/api/users/${id}`)
* return res.json()
* }
*
* const { data, loading, error, execute } = useAsync(fetchUser, ['userId'])
*
* <div>
* {loading && <Spinner />}
* {error && <Alert severity="error">{error.message}</Alert>}
* {data && <UserProfile user={data} />}
* <Button onClick={() => execute('123')}>Fetch User</Button>
* </div>
*
* @example
* // With automatic execution on mount
* const { data: posts, loading, error } = useAsync(
* async () => {
* const res = await fetch('/api/posts')
* return res.json()
* },
* [],
* { immediate: true }
* )
*
* @example
* // With error handling and retry
* const { data, loading, error, execute } = useAsync(
* async () => {
* try {
* const res = await fetch('/api/data')
* if (!res.ok) throw new Error(`HTTP ${res.status}`)
* return res.json()
* } catch (err) {
* throw err
* }
* }
* )
*
* const handleRetry = async () => {
* await execute()
* }
*/
import { useState, useCallback, useEffect, useRef } from 'react'
export interface UseAsyncOptions {
/** Execute immediately on mount (default: false) */
immediate?: boolean
/** Reset error on retry (default: true) */
resetErrorOnRetry?: boolean
/** Reset data on retry (default: false) */
resetDataOnRetry?: boolean
}
export interface UseAsyncReturn<T> {
/** Result data from async function */
data: T | undefined
/** Loading state */
loading: boolean
/** Error object if function threw */
error: Error | undefined
/** Execute the async function */
execute: (...args: any[]) => Promise<T | undefined>
/** Reset data, error, and loading state */
reset: () => void
}
/**
* Hook for managing async function execution with state
* @template T - The return type of the async function
* @param asyncFunction - Async function to execute
* @param deps - Dependency array for re-execution (default: [])
* @param options - Configuration options
* @returns Object containing data, loading, error, and execute method
*/
export function useAsync<T>(
asyncFunction: (...args: any[]) => Promise<T>,
deps: any[] = [],
options: UseAsyncOptions = {}
): UseAsyncReturn<T> {
const { immediate = false, resetErrorOnRetry = true, resetDataOnRetry = false } = options
const [data, setData] = useState<T | undefined>(undefined)
const [loading, setLoading] = useState<boolean>(false)
const [error, setError] = useState<Error | undefined>(undefined)
const isMountedRef = useRef<boolean>(true)
// Cleanup on unmount
useEffect(() => {
return () => {
isMountedRef.current = false
}
}, [])
const execute = useCallback(
async (...args: any[]): Promise<T | undefined> => {
// Reset states if requested
if (resetErrorOnRetry) setError(undefined)
if (resetDataOnRetry) setData(undefined)
setLoading(true)
try {
const result = await asyncFunction(...args)
// Only update state if component is still mounted
if (isMountedRef.current) {
setData(result)
setError(undefined)
return result
}
} catch (err) {
// Only update state if component is still mounted
if (isMountedRef.current) {
const error = err instanceof Error ? err : new Error(String(err))
setError(error)
setData(undefined)
}
} finally {
if (isMountedRef.current) {
setLoading(false)
}
}
return undefined
},
[asyncFunction, resetErrorOnRetry, resetDataOnRetry]
)
const reset = useCallback(() => {
setData(undefined)
setError(undefined)
setLoading(false)
}, [])
// Execute immediately if requested
useEffect(() => {
if (immediate) {
execute()
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps)
return {
data,
loading,
error,
execute,
reset,
}
}