@umituz/react-native-firebase 1.13.148 → 1.13.150
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/auth/infrastructure/services/apple-auth.service.ts +10 -2
- package/src/auth/infrastructure/services/google-auth.service.ts +10 -2
- package/src/auth/infrastructure/services/reauthentication.service.ts +5 -1
- package/src/auth/presentation/hooks/shared/auth-hooks.util.ts +60 -0
- package/src/auth/presentation/hooks/shared/hook-utils.util.ts +14 -216
- package/src/auth/presentation/hooks/shared/safe-state-hooks.util.ts +76 -0
- package/src/auth/presentation/hooks/shared/state-hooks.util.ts +97 -0
- package/src/domain/utils/async-executor.util.ts +16 -169
- package/src/domain/utils/error-handler.util.ts +18 -170
- package/src/domain/utils/error-handlers/error-checkers.ts +120 -0
- package/src/domain/utils/error-handlers/error-converters.ts +48 -0
- package/src/domain/utils/error-handlers/error-messages.ts +18 -0
- package/src/domain/utils/executors/advanced-executors.util.ts +59 -0
- package/src/domain/utils/executors/basic-executors.util.ts +56 -0
- package/src/domain/utils/executors/batch-executors.util.ts +42 -0
- package/src/domain/utils/executors/error-converters.util.ts +45 -0
- package/src/domain/utils/result/result-creators.ts +49 -0
- package/src/domain/utils/result/result-helpers.ts +60 -0
- package/src/domain/utils/result/result-types.ts +40 -0
- package/src/domain/utils/result.util.ts +28 -127
- package/src/domain/utils/validation.util.ts +38 -209
- package/src/domain/utils/validators/composite.validator.ts +24 -0
- package/src/domain/utils/validators/firebase.validator.ts +45 -0
- package/src/domain/utils/validators/generic.validator.ts +59 -0
- package/src/domain/utils/validators/string.validator.ts +25 -0
- package/src/domain/utils/validators/url.validator.ts +36 -0
- package/src/domain/utils/validators/user-input.validator.ts +54 -0
- package/src/firestore/infrastructure/middleware/QuotaTrackingMiddleware.ts +10 -3
- package/src/firestore/utils/deduplication/timer-manager.util.ts +2 -2
- package/src/firestore/utils/pagination.helper.ts +3 -1
- package/src/infrastructure/config/FirebaseClient.ts +28 -189
- package/src/infrastructure/config/clients/FirebaseClientSingleton.ts +82 -0
- package/src/infrastructure/config/services/FirebaseInitializationService.ts +115 -0
- package/src/init/createFirebaseInitModule.ts +9 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-firebase",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.150",
|
|
4
4
|
"description": "Unified Firebase package for React Native apps - Auth and Firestore services using Firebase JS SDK (no native modules).",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -75,10 +75,18 @@ export class AppleAuthService {
|
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
const userCredential = await signInWithCredential(auth, credential);
|
|
78
|
+
|
|
79
|
+
// Check if user is new by comparing metadata timestamps
|
|
80
|
+
// Convert to timestamps for reliable comparison (string comparison can be unreliable)
|
|
81
|
+
const creationTime = userCredential.user.metadata.creationTime;
|
|
82
|
+
const lastSignInTime = userCredential.user.metadata.lastSignInTime;
|
|
83
|
+
const isNewUser = creationTime && lastSignInTime
|
|
84
|
+
? new Date(creationTime).getTime() === new Date(lastSignInTime).getTime()
|
|
85
|
+
: false;
|
|
86
|
+
|
|
78
87
|
return {
|
|
79
88
|
userCredential,
|
|
80
|
-
isNewUser
|
|
81
|
-
userCredential.user.metadata.lastSignInTime
|
|
89
|
+
isNewUser
|
|
82
90
|
};
|
|
83
91
|
});
|
|
84
92
|
|
|
@@ -46,10 +46,18 @@ export class GoogleAuthService extends ConfigurableService<GoogleAuthConfig> {
|
|
|
46
46
|
const result = await executeAuthOperation(async () => {
|
|
47
47
|
const credential = GoogleAuthProvider.credential(idToken);
|
|
48
48
|
const userCredential = await signInWithCredential(auth, credential);
|
|
49
|
+
|
|
50
|
+
// Check if user is new by comparing metadata timestamps
|
|
51
|
+
// Convert to timestamps for reliable comparison (string comparison can be unreliable)
|
|
52
|
+
const creationTime = userCredential.user.metadata.creationTime;
|
|
53
|
+
const lastSignInTime = userCredential.user.metadata.lastSignInTime;
|
|
54
|
+
const isNewUser = creationTime && lastSignInTime
|
|
55
|
+
? new Date(creationTime).getTime() === new Date(lastSignInTime).getTime()
|
|
56
|
+
: false;
|
|
57
|
+
|
|
49
58
|
return {
|
|
50
59
|
userCredential,
|
|
51
|
-
isNewUser
|
|
52
|
-
userCredential.user.metadata.lastSignInTime
|
|
60
|
+
isNewUser
|
|
53
61
|
};
|
|
54
62
|
});
|
|
55
63
|
return this.convertToGoogleAuthResult(result);
|
|
@@ -99,7 +99,11 @@ export async function getAppleReauthCredential(): Promise<ReauthCredentialResult
|
|
|
99
99
|
};
|
|
100
100
|
} catch (error: unknown) {
|
|
101
101
|
const err = error instanceof Error ? error : new Error(String(error));
|
|
102
|
-
const
|
|
102
|
+
const errorInfo = {
|
|
103
|
+
code: (err as { code?: string }).code ?? '',
|
|
104
|
+
message: err.message
|
|
105
|
+
};
|
|
106
|
+
const code = isCancelledError(errorInfo) ? "auth/cancelled" : "auth/failed";
|
|
103
107
|
return {
|
|
104
108
|
success: false,
|
|
105
109
|
error: { code, message: err.message }
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auth-Specific Hooks
|
|
3
|
+
* React hooks for Firebase Auth state management
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useRef, useEffect, useCallback } from 'react';
|
|
7
|
+
import { onAuthStateChanged, type Auth, type User } from 'firebase/auth';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Create an auth state handler
|
|
11
|
+
*/
|
|
12
|
+
export function createAuthStateHandler(
|
|
13
|
+
setState: (user: User | null) => void,
|
|
14
|
+
setLoading: (loading: boolean) => void,
|
|
15
|
+
setError: (error: Error | null) => void
|
|
16
|
+
): (user: User | null) => void {
|
|
17
|
+
return (user: User | null) => {
|
|
18
|
+
try {
|
|
19
|
+
setState(user);
|
|
20
|
+
setError(null);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
const error = err instanceof Error ? err : new Error('Auth state update failed');
|
|
23
|
+
setError(error);
|
|
24
|
+
} finally {
|
|
25
|
+
setLoading(false);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hook for managing auth listener lifecycle
|
|
32
|
+
*/
|
|
33
|
+
export function useAuthListener(
|
|
34
|
+
auth: Auth | null,
|
|
35
|
+
onAuthStateChange: (user: User | null) => void
|
|
36
|
+
): void {
|
|
37
|
+
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// Cleanup previous listener
|
|
41
|
+
if (unsubscribeRef.current) {
|
|
42
|
+
unsubscribeRef.current();
|
|
43
|
+
unsubscribeRef.current = null;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!auth) {
|
|
47
|
+
onAuthStateChange(null);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
unsubscribeRef.current = onAuthStateChanged(auth, onAuthStateChange);
|
|
52
|
+
|
|
53
|
+
return () => {
|
|
54
|
+
if (unsubscribeRef.current) {
|
|
55
|
+
unsubscribeRef.current();
|
|
56
|
+
unsubscribeRef.current = null;
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}, [auth, onAuthStateChange]);
|
|
60
|
+
}
|
|
@@ -1,222 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Shared Hook Utilities
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Re-exports all hook utilities for backward compatibility
|
|
4
|
+
* @deprecated Import from specific files instead
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
// State Management Hooks
|
|
8
|
+
export type { HookState, HookStateActions } from './state-hooks.util';
|
|
9
|
+
export { useHookState, useAsyncOperation } from './state-hooks.util';
|
|
9
10
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Hook state actions
|
|
21
|
-
*/
|
|
22
|
-
export interface HookStateActions<T> {
|
|
23
|
-
setValue: (value: T) => void;
|
|
24
|
-
setLoading: (loading: boolean) => void;
|
|
25
|
-
setError: (error: Error | null) => void;
|
|
26
|
-
clearError: () => void;
|
|
27
|
-
reset: () => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Create a stateful hook with loading and error handling
|
|
32
|
-
*/
|
|
33
|
-
export function useHookState<T>(
|
|
34
|
-
initialValue: T
|
|
35
|
-
): [HookState<T>, HookStateActions<T>] {
|
|
36
|
-
const [value, setValue] = useState<T>(initialValue);
|
|
37
|
-
const [loading, setLoading] = useState<boolean>(false);
|
|
38
|
-
const [error, setError] = useState<Error | null>(null);
|
|
39
|
-
|
|
40
|
-
const clearError = useCallback(() => {
|
|
41
|
-
setError(null);
|
|
42
|
-
}, []);
|
|
43
|
-
|
|
44
|
-
const reset = useCallback(() => {
|
|
45
|
-
setValue(initialValue);
|
|
46
|
-
setLoading(false);
|
|
47
|
-
setError(null);
|
|
48
|
-
}, [initialValue]);
|
|
49
|
-
|
|
50
|
-
const state: HookState<T> = { value, loading, error };
|
|
51
|
-
const actions: HookStateActions<T> = {
|
|
52
|
-
setValue,
|
|
53
|
-
setLoading,
|
|
54
|
-
setError,
|
|
55
|
-
clearError,
|
|
56
|
-
reset,
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
return [state, actions];
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Hook for managing cleanup callbacks
|
|
64
|
-
*/
|
|
65
|
-
export function useCleanup(
|
|
66
|
-
cleanupFn: () => void,
|
|
67
|
-
deps: React.DependencyList = []
|
|
68
|
-
): void {
|
|
69
|
-
useEffect(() => {
|
|
70
|
-
return () => {
|
|
71
|
-
cleanupFn();
|
|
72
|
-
};
|
|
73
|
-
}, deps);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Hook for managing async operation state
|
|
78
|
-
*/
|
|
79
|
-
export function useAsyncOperation<T = void>() {
|
|
80
|
-
const [loading, setLoading] = useState<boolean>(false);
|
|
81
|
-
const [error, setError] = useState<Error | null>(null);
|
|
82
|
-
const operationRef = useRef<Promise<T> | null>(null);
|
|
83
|
-
|
|
84
|
-
const execute = useCallback(async (operation: () => Promise<T>): Promise<T> => {
|
|
85
|
-
setLoading(true);
|
|
86
|
-
setError(null);
|
|
87
|
-
|
|
88
|
-
try {
|
|
89
|
-
const promise = operation();
|
|
90
|
-
operationRef.current = promise;
|
|
91
|
-
const result = await promise;
|
|
92
|
-
return result;
|
|
93
|
-
} catch (err) {
|
|
94
|
-
const error = err instanceof Error ? err : new Error(String(err));
|
|
95
|
-
setError(error);
|
|
96
|
-
throw error;
|
|
97
|
-
} finally {
|
|
98
|
-
setLoading(false);
|
|
99
|
-
operationRef.current = null;
|
|
100
|
-
}
|
|
101
|
-
}, []);
|
|
102
|
-
|
|
103
|
-
const clearError = useCallback(() => {
|
|
104
|
-
setError(null);
|
|
105
|
-
}, []);
|
|
106
|
-
|
|
107
|
-
return {
|
|
108
|
-
loading,
|
|
109
|
-
error,
|
|
110
|
-
execute,
|
|
111
|
-
clearError,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Hook for debounced value updates
|
|
117
|
-
*/
|
|
118
|
-
export function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
119
|
-
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
120
|
-
|
|
121
|
-
useEffect(() => {
|
|
122
|
-
const handler = setTimeout(() => {
|
|
123
|
-
setDebouncedValue(value);
|
|
124
|
-
}, delayMs);
|
|
125
|
-
|
|
126
|
-
return () => {
|
|
127
|
-
clearTimeout(handler);
|
|
128
|
-
};
|
|
129
|
-
}, [value, delayMs]);
|
|
130
|
-
|
|
131
|
-
return debouncedValue;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* Hook for tracking mounted state
|
|
136
|
-
*/
|
|
137
|
-
export function useIsMounted(): () => boolean {
|
|
138
|
-
const isMountedRef = useRef<boolean>(true);
|
|
139
|
-
|
|
140
|
-
useEffect(() => {
|
|
141
|
-
isMountedRef.current = true;
|
|
142
|
-
return () => {
|
|
143
|
-
isMountedRef.current = false;
|
|
144
|
-
};
|
|
145
|
-
}, []);
|
|
146
|
-
|
|
147
|
-
return useCallback(() => isMountedRef.current, []);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* Hook for safe state updates (only if mounted)
|
|
152
|
-
*/
|
|
153
|
-
export function useSafeState<T>(
|
|
154
|
-
initialValue: T
|
|
155
|
-
): [T, (value: T | ((prev: T) => T)) => void] {
|
|
156
|
-
const isMounted = useIsMounted();
|
|
157
|
-
const [state, setState] = useState<T>(initialValue);
|
|
158
|
-
|
|
159
|
-
const setSafeState = useCallback(
|
|
160
|
-
(value: T | ((prev: T) => T)) => {
|
|
161
|
-
if (isMounted()) {
|
|
162
|
-
setState(value);
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
[isMounted]
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
return [state, setSafeState];
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Create an auth state handler
|
|
173
|
-
*/
|
|
174
|
-
export function createAuthStateHandler(
|
|
175
|
-
setState: (user: User | null) => void,
|
|
176
|
-
setLoading: (loading: boolean) => void,
|
|
177
|
-
setError: (error: Error | null) => void
|
|
178
|
-
): (user: User | null) => void {
|
|
179
|
-
return (user: User | null) => {
|
|
180
|
-
try {
|
|
181
|
-
setState(user);
|
|
182
|
-
setError(null);
|
|
183
|
-
} catch (err) {
|
|
184
|
-
const error = err instanceof Error ? err : new Error('Auth state update failed');
|
|
185
|
-
setError(error);
|
|
186
|
-
} finally {
|
|
187
|
-
setLoading(false);
|
|
188
|
-
}
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Hook for managing auth listener lifecycle
|
|
194
|
-
*/
|
|
195
|
-
export function useAuthListener(
|
|
196
|
-
auth: Auth | null,
|
|
197
|
-
onAuthStateChange: (user: User | null) => void
|
|
198
|
-
): void {
|
|
199
|
-
const unsubscribeRef = useRef<(() => void) | null>(null);
|
|
200
|
-
|
|
201
|
-
useEffect(() => {
|
|
202
|
-
// Cleanup previous listener
|
|
203
|
-
if (unsubscribeRef.current) {
|
|
204
|
-
unsubscribeRef.current();
|
|
205
|
-
unsubscribeRef.current = null;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
if (!auth) {
|
|
209
|
-
onAuthStateChange(null);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
unsubscribeRef.current = onAuthStateChanged(auth, onAuthStateChange);
|
|
11
|
+
// Safe State Hooks
|
|
12
|
+
export {
|
|
13
|
+
useIsMounted,
|
|
14
|
+
useSafeState,
|
|
15
|
+
useDebouncedValue,
|
|
16
|
+
useCleanup,
|
|
17
|
+
} from './safe-state-hooks.util';
|
|
214
18
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
unsubscribeRef.current();
|
|
218
|
-
unsubscribeRef.current = null;
|
|
219
|
-
}
|
|
220
|
-
};
|
|
221
|
-
}, [auth, onAuthStateChange]);
|
|
222
|
-
}
|
|
19
|
+
// Auth Hooks
|
|
20
|
+
export { createAuthStateHandler, useAuthListener } from './auth-hooks.util';
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Safe State Hooks
|
|
3
|
+
* React hooks for safe state management and mounted state tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback, useRef, useEffect } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook for tracking mounted state
|
|
10
|
+
*/
|
|
11
|
+
export function useIsMounted(): () => boolean {
|
|
12
|
+
const isMountedRef = useRef<boolean>(true);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
isMountedRef.current = true;
|
|
16
|
+
return () => {
|
|
17
|
+
isMountedRef.current = false;
|
|
18
|
+
};
|
|
19
|
+
}, []);
|
|
20
|
+
|
|
21
|
+
return useCallback(() => isMountedRef.current, []);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Hook for safe state updates (only if mounted)
|
|
26
|
+
*/
|
|
27
|
+
export function useSafeState<T>(
|
|
28
|
+
initialValue: T
|
|
29
|
+
): [T, (value: T | ((prev: T) => T)) => void] {
|
|
30
|
+
const isMounted = useIsMounted();
|
|
31
|
+
const [state, setState] = useState<T>(initialValue);
|
|
32
|
+
|
|
33
|
+
const setSafeState = useCallback(
|
|
34
|
+
(value: T | ((prev: T) => T)) => {
|
|
35
|
+
if (isMounted()) {
|
|
36
|
+
setState(value);
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
[isMounted]
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
return [state, setSafeState];
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Hook for debounced value updates
|
|
47
|
+
*/
|
|
48
|
+
export function useDebouncedValue<T>(value: T, delayMs: number): T {
|
|
49
|
+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
|
|
50
|
+
|
|
51
|
+
useEffect(() => {
|
|
52
|
+
const handler = setTimeout(() => {
|
|
53
|
+
setDebouncedValue(value);
|
|
54
|
+
}, delayMs);
|
|
55
|
+
|
|
56
|
+
return () => {
|
|
57
|
+
clearTimeout(handler);
|
|
58
|
+
};
|
|
59
|
+
}, [value, delayMs]);
|
|
60
|
+
|
|
61
|
+
return debouncedValue;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Hook for managing cleanup callbacks
|
|
66
|
+
*/
|
|
67
|
+
export function useCleanup(
|
|
68
|
+
cleanupFn: () => void,
|
|
69
|
+
deps: React.DependencyList = []
|
|
70
|
+
): void {
|
|
71
|
+
useEffect(() => {
|
|
72
|
+
return () => {
|
|
73
|
+
cleanupFn();
|
|
74
|
+
};
|
|
75
|
+
}, deps);
|
|
76
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Management Hooks
|
|
3
|
+
* React hooks for managing component state with loading and error handling
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { useState, useCallback, useRef } from 'react';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Hook state management result
|
|
10
|
+
*/
|
|
11
|
+
export interface HookState<T> {
|
|
12
|
+
value: T;
|
|
13
|
+
loading: boolean;
|
|
14
|
+
error: Error | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Hook state actions
|
|
19
|
+
*/
|
|
20
|
+
export interface HookStateActions<T> {
|
|
21
|
+
setValue: (value: T) => void;
|
|
22
|
+
setLoading: (loading: boolean) => void;
|
|
23
|
+
setError: (error: Error | null) => void;
|
|
24
|
+
clearError: () => void;
|
|
25
|
+
reset: () => void;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Create a stateful hook with loading and error handling
|
|
30
|
+
*/
|
|
31
|
+
export function useHookState<T>(
|
|
32
|
+
initialValue: T
|
|
33
|
+
): [HookState<T>, HookStateActions<T>] {
|
|
34
|
+
const [value, setValue] = useState<T>(initialValue);
|
|
35
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
36
|
+
const [error, setError] = useState<Error | null>(null);
|
|
37
|
+
|
|
38
|
+
const clearError = useCallback(() => {
|
|
39
|
+
setError(null);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
const reset = useCallback(() => {
|
|
43
|
+
setValue(initialValue);
|
|
44
|
+
setLoading(false);
|
|
45
|
+
setError(null);
|
|
46
|
+
}, [initialValue]);
|
|
47
|
+
|
|
48
|
+
const state: HookState<T> = { value, loading, error };
|
|
49
|
+
const actions: HookStateActions<T> = {
|
|
50
|
+
setValue,
|
|
51
|
+
setLoading,
|
|
52
|
+
setError,
|
|
53
|
+
clearError,
|
|
54
|
+
reset,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
return [state, actions];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Hook for managing async operation state
|
|
62
|
+
*/
|
|
63
|
+
export function useAsyncOperation<T = void>() {
|
|
64
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
65
|
+
const [error, setError] = useState<Error | null>(null);
|
|
66
|
+
const operationRef = useRef<Promise<T> | null>(null);
|
|
67
|
+
|
|
68
|
+
const execute = useCallback(async (operation: () => Promise<T>): Promise<T> => {
|
|
69
|
+
setLoading(true);
|
|
70
|
+
setError(null);
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const promise = operation();
|
|
74
|
+
operationRef.current = promise;
|
|
75
|
+
const result = await promise;
|
|
76
|
+
return result;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
79
|
+
setError(error);
|
|
80
|
+
throw error;
|
|
81
|
+
} finally {
|
|
82
|
+
setLoading(false);
|
|
83
|
+
operationRef.current = null;
|
|
84
|
+
}
|
|
85
|
+
}, []);
|
|
86
|
+
|
|
87
|
+
const clearError = useCallback(() => {
|
|
88
|
+
setError(null);
|
|
89
|
+
}, []);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
loading,
|
|
93
|
+
error,
|
|
94
|
+
execute,
|
|
95
|
+
clearError,
|
|
96
|
+
};
|
|
97
|
+
}
|