@@ -15,7 +15,8 @@ export enum UploadStatus {
1515 REQUESTING_PERMISSION = 'requesting_permission' ,
1616 UPLOADING = 'uploading' ,
1717 SUCCESS = 'success' ,
18- ERROR = 'error'
18+ ERROR = 'error' ,
19+ CANCELLED = 'cancelled'
1920}
2021
2122interface UseFileUploadOptions {
@@ -45,19 +46,34 @@ export const useFileUpload = ({ onUploadComplete }: UseFileUploadOptions = {}):
4546 const [ error , setError ] = useState < string | null > ( null ) ;
4647 // Use a ref to track if upload should be canceled
4748 const cancelRef = useRef < boolean > ( false ) ;
49+ // Use a ref to hold the AbortController
50+ const abortControllerRef = useRef < AbortController | null > ( null ) ;
4851
4952 const reset = useCallback ( ( ) => {
5053 setFile ( null ) ;
5154 setStatus ( UploadStatus . IDLE ) ;
5255 setProgress ( 0 ) ;
5356 setError ( null ) ;
5457 cancelRef . current = false ;
58+
59+ // Abort any pending requests from previous uploads
60+ if ( abortControllerRef . current ) {
61+ abortControllerRef . current . abort ( ) ;
62+ abortControllerRef . current = null ;
63+ }
5564 } , [ ] ) ;
5665
5766 const cancelUpload = useCallback ( ( ) => {
67+ cancelRef . current = true ;
68+
69+ // Abort the ongoing request if there's one
70+ if ( abortControllerRef . current ) {
71+ abortControllerRef . current . abort ( ) ;
72+ abortControllerRef . current = null ;
73+ }
74+
5875 if ( status === UploadStatus . UPLOADING || status === UploadStatus . REQUESTING_PERMISSION ) {
59- cancelRef . current = true ;
60- setStatus ( UploadStatus . IDLE ) ;
76+ setStatus ( UploadStatus . CANCELLED ) ;
6177 setProgress ( 0 ) ;
6278 } else {
6379 reset ( ) ;
@@ -89,78 +105,117 @@ export const useFileUpload = ({ onUploadComplete }: UseFileUploadOptions = {}):
89105 setError ( null ) ;
90106 } , [ t ] ) ;
91107
108+ // Extract the progress callback outside of uploadFile to reduce complexity
109+ const createProgressCallback = useCallback ( ( signal : AbortSignal ) : UploadProgressCallback => {
110+ return ( progress : number ) => {
111+ if ( ! cancelRef . current && ! signal . aborted ) {
112+ setProgress ( progress ) ;
113+ }
114+ } ;
115+ } , [ ] ) ;
116+
117+ // Helper to check if the upload has been canceled
118+ const isUploadCanceled = useCallback ( ( signal : AbortSignal ) : boolean => {
119+ return cancelRef . current || signal . aborted ;
120+ } , [ ] ) ;
121+
92122 const uploadFile = useCallback ( async ( ) => {
123+ // Don't proceed if there's no file or if the status is CANCELLED
93124 if ( ! file ) {
94125 setError ( t ( 'upload.error.noFile' ) ) ;
95126 return ;
96127 }
128+
129+ // If currently in CANCELLED state, we need to reset first
130+ if ( status === UploadStatus . CANCELLED ) {
131+ reset ( ) ;
132+ return ;
133+ }
97134
98135 // Reset cancel flag
99136 cancelRef . current = false ;
137+
138+ // Create a new AbortController for this upload request
139+ abortControllerRef . current = new AbortController ( ) ;
140+ const { signal } = abortControllerRef . current ;
100141
101142 try {
102143 setStatus ( UploadStatus . REQUESTING_PERMISSION ) ;
103144
104- // Check for permissions
105- let hasPermission = await checkFilePermissions ( ) ;
145+ // Check and request permissions if needed
146+ const hasPermission = await checkPermissions ( ) ;
106147
107148 if ( ! hasPermission ) {
108- // Request permissions
109- hasPermission = await requestFilePermissions ( ) ;
110-
111- if ( ! hasPermission ) {
112- setStatus ( UploadStatus . ERROR ) ;
113- setError ( t ( 'upload.error.permissionDenied' ) ) ;
114- return ;
115- }
149+ setStatus ( UploadStatus . ERROR ) ;
150+ setError ( t ( 'upload.error.permissionDenied' ) ) ;
151+ return ;
116152 }
117153
118154 // Check if canceled during permission check
119- if ( cancelRef . current ) {
120- setStatus ( UploadStatus . IDLE ) ;
155+ if ( isUploadCanceled ( signal ) ) {
156+ setStatus ( UploadStatus . CANCELLED ) ;
121157 return ;
122158 }
123159
124160 setStatus ( UploadStatus . UPLOADING ) ;
125161 setProgress ( 0 ) ;
126162
127- // Create a progress callback for the upload
128- const updateProgress : UploadProgressCallback = ( progress ) => {
129- // Only update progress if not canceled
130- if ( ! cancelRef . current ) {
131- setProgress ( progress ) ;
132- }
133- } ;
163+ // Get a progress callback
164+ const updateProgress = createProgressCallback ( signal ) ;
134165
135- // Upload the file using the API service
136- const result = await uploadReport ( file , updateProgress ) ;
166+ // Upload the file
167+ const result = await uploadReport ( file , updateProgress , signal ) ;
137168
138169 // Check if canceled during upload
139- if ( cancelRef . current ) {
140- setStatus ( UploadStatus . IDLE ) ;
170+ if ( isUploadCanceled ( signal ) ) {
171+ setStatus ( UploadStatus . CANCELLED ) ;
141172 return ;
142173 }
143174
144- // Set progress to 100% to indicate completion
175+ // Success
145176 setProgress ( 1 ) ;
146177 setStatus ( UploadStatus . SUCCESS ) ;
147178
148- // Notify parent component if callback provided
149179 if ( onUploadComplete ) {
150180 onUploadComplete ( result ) ;
151181 }
152182 } catch ( error ) {
153- // Don't show error if canceled
154- if ( ! cancelRef . current ) {
155- setStatus ( UploadStatus . ERROR ) ;
156- setError (
157- error instanceof Error
158- ? error . message
159- : t ( 'upload.error.unknown' )
160- ) ;
161- }
183+ handleUploadError ( error as Error , signal ) ;
184+ } finally {
185+ cleanupAbortController ( signal ) ;
186+ }
187+ } , [ file , status , onUploadComplete , t , createProgressCallback , isUploadCanceled , reset ] ) ;
188+
189+ // Helper to handle file permissions
190+ const checkPermissions = useCallback ( async ( ) : Promise < boolean > => {
191+ let hasPermission = await checkFilePermissions ( ) ;
192+
193+ if ( ! hasPermission ) {
194+ hasPermission = await requestFilePermissions ( ) ;
162195 }
163- } , [ file , onUploadComplete , t ] ) ;
196+
197+ return hasPermission ;
198+ } , [ ] ) ;
199+
200+ // Helper to handle upload errors
201+ const handleUploadError = useCallback ( ( error : Error , signal : AbortSignal ) => {
202+ // Don't show error for aborted requests
203+ if ( error instanceof DOMException && error . name === 'AbortError' ) {
204+ return ;
205+ }
206+
207+ if ( ! isUploadCanceled ( signal ) ) {
208+ setStatus ( UploadStatus . ERROR ) ;
209+ setError ( error instanceof Error ? error . message : t ( 'upload.error.unknown' ) ) ;
210+ }
211+ } , [ t , isUploadCanceled ] ) ;
212+
213+ // Helper to clean up the AbortController
214+ const cleanupAbortController = useCallback ( ( signal : AbortSignal ) => {
215+ if ( abortControllerRef . current ?. signal === signal ) {
216+ abortControllerRef . current = null ;
217+ }
218+ } , [ ] ) ;
164219
165220 return {
166221 file,
@@ -173,4 +228,4 @@ export const useFileUpload = ({ onUploadComplete }: UseFileUploadOptions = {}):
173228 formatFileSize,
174229 cancelUpload
175230 } ;
176- } ;
231+ } ;
0 commit comments