fix(zim): stabilize uploader state to avoid cancel on tab refocus

Also improves surfacing of upload error messages
This commit is contained in:
jakeaturner 2026-06-20 03:36:40 +00:00
parent a7d05859d4
commit fad9e30ddc
No known key found for this signature in database
GPG Key ID: B1072EBDEECE328D
3 changed files with 23 additions and 9 deletions

View File

@ -16,6 +16,16 @@ export default function ZimUploader({ onUploadComplete, existingFilenames }: Zim
existingFilenamesRef.current = existingFilenames existingFilenamesRef.current = existingFilenames
}, [existingFilenames]) }, [existingFilenames])
// Keep the latest callback in a ref so the Uppy lifecycle effect below does
// not depend on it. The parent passes a new inline function on every render,
// and if that identity were in the effect deps, a re-render mid-upload (e.g.
// a window-focus refetch) would tear down and `uppy.destroy()` the instance,
// aborting the in-flight upload.
const onUploadCompleteRef = useRef(onUploadComplete)
useEffect(() => {
onUploadCompleteRef.current = onUploadComplete
}, [onUploadComplete])
const [uppy] = useState(() => const [uppy] = useState(() =>
new Uppy({ new Uppy({
restrictions: { restrictions: {
@ -53,9 +63,17 @@ export default function ZimUploader({ onUploadComplete, existingFilenames }: Zim
} }
} }
} }
const handleComplete = (result: { successful: Array<{ response?: { body?: { added?: number } } }> }) => { const handleComplete = (result: {
successful: Array<{ response?: { body?: { added?: number } } }>
failed: Array<unknown>
}) => {
// Uppy emits `complete` even when uploads fail or are aborted. Only treat
// the run as a success when nothing failed — otherwise leave the uploader
// open so the Dashboard can surface the error instead of closing it with a
// false "Upload complete" toast.
if (result.failed.length > 0) return
const added = result.successful.reduce((sum, f) => sum + (f.response?.body?.added ?? 0), 0) const added = result.successful.reduce((sum, f) => sum + (f.response?.body?.added ?? 0), 0)
onUploadComplete(added) onUploadCompleteRef.current(added)
} }
uppy.on('file-added', handleFileAdded) uppy.on('file-added', handleFileAdded)
uppy.on('complete', handleComplete) uppy.on('complete', handleComplete)
@ -64,7 +82,7 @@ export default function ZimUploader({ onUploadComplete, existingFilenames }: Zim
uppy.off('complete', handleComplete) uppy.off('complete', handleComplete)
uppy.destroy() uppy.destroy()
} }
}, [uppy, onUploadComplete]) }, [uppy])
return ( return (
<Dashboard <Dashboard

View File

@ -30,6 +30,7 @@ export default function ZimPage() {
const { data, isLoading } = useQuery<ZimFileWithMetadata[]>({ const { data, isLoading } = useQuery<ZimFileWithMetadata[]>({
queryKey: ['zim-files'], queryKey: ['zim-files'],
queryFn: getFiles, queryFn: getFiles,
refetchOnWindowFocus: false,
}) })
async function getFiles() { async function getFiles() {

View File

@ -35,6 +35,7 @@
"@uppy/core": "5.2.0", "@uppy/core": "5.2.0",
"@uppy/dashboard": "5.1.0", "@uppy/dashboard": "5.1.0",
"@uppy/react": "5.1.1", "@uppy/react": "5.1.1",
"@uppy/xhr-upload": "5.2.0",
"@vinejs/vine": "3.0.1", "@vinejs/vine": "3.0.1",
"@vitejs/plugin-react": "4.7.0", "@vitejs/plugin-react": "4.7.0",
"autoprefixer": "10.5.0", "autoprefixer": "10.5.0",
@ -4589,7 +4590,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -4606,7 +4606,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -4623,7 +4622,6 @@
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"optional": true, "optional": true,
"os": [ "os": [
@ -4716,7 +4714,6 @@
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -4733,7 +4730,6 @@
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [
@ -4750,7 +4746,6 @@
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true,
"license": "Apache-2.0 AND MIT", "license": "Apache-2.0 AND MIT",
"optional": true, "optional": true,
"os": [ "os": [