Fix DownloadButton reverting to "Скачать" after completion

The button disabled its own status polling once a download reached a
terminal state and fell back to the video prop it was first rendered with.
On the video detail page that prop only refreshes on a full page reload
(only ['feed'] was invalidated, not ['video', id]), so the button showed
"Скачать" again right after a real completion until the user refreshed.

Keep the polling query enabled permanently once a download starts tracking
(refetchInterval alone already stops the ticking on a terminal status) and
use its cache as the source of truth instead of the prop, so the button
reflects the fetched status directly rather than depending on an unrelated
query being invalidated and refetched in time.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
vrubelroman 2026-09-16 19:37:33 +00:00
parent e333296170
commit 10c16ba2cb

View file

@ -1,4 +1,4 @@
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'
import type { FeedVideoDto } from '../api/client'
import { downloadVideo, getDownloadStatus } from '../api/client'
@ -16,43 +16,43 @@ interface Props {
function DownloadButton({ video }: Props) {
const queryClient = useQueryClient()
const [polling, setPolling] = useState(ACTIVE_STATUSES.includes(video.local.status))
// Once we start tracking a download (it was already active on load, or the
// user just clicked "Скачать"), the query stays enabled for good --
// refetchInterval alone stops the ticking once the status is terminal.
// Disabling the query on completion used to make the button fall back to
// the *stale* `video` prop instead of the query's own fresh cache, which
// showed "Скачать" again until the page was manually reloaded.
const [tracking, setTracking] = useState(ACTIVE_STATUSES.includes(video.local.status))
const statusQuery = useQuery({
queryKey: ['download-status', video.youtube_video_id],
queryFn: () => getDownloadStatus(video.youtube_video_id),
enabled: polling,
refetchInterval: (query) => (query.state.data && ACTIVE_STATUSES.includes(query.state.data.status) ? 2000 : false),
initialData: polling
? {
status: video.local.status,
progress_percent: video.local.progress_percent,
media_url: video.local.media_url,
error_message: null,
}
: undefined,
enabled: tracking,
refetchInterval: (query) =>
query.state.data && ACTIVE_STATUSES.includes(query.state.data.status) ? 2000 : false,
})
const downloadMutation = useMutation({
mutationFn: () => downloadVideo(video.youtube_video_id),
onSuccess: (data) => {
queryClient.setQueryData(['download-status', video.youtube_video_id], data)
setPolling(true)
setTracking(true)
},
})
const current = polling ? statusQuery.data : undefined
const status = current?.status ?? video.local.status
const percent = current?.progress_percent ?? video.local.progress_percent
const status = statusQuery.data?.status ?? video.local.status
const percent = statusQuery.data?.progress_percent ?? video.local.progress_percent
const prevStatus = useRef(status)
useEffect(() => {
if (current && !ACTIVE_STATUSES.includes(current.status)) {
setPolling(false)
if (current.status === 'completed' || current.status === 'failed') {
if (prevStatus.current !== status) {
prevStatus.current = status
if (status === 'completed' || status === 'failed') {
queryClient.invalidateQueries({ queryKey: ['feed'] })
queryClient.invalidateQueries({ queryKey: ['video', video.youtube_video_id] })
}
}
}, [current?.status, queryClient])
}, [status, queryClient, video.youtube_video_id])
if (status === 'completed') {
return <span className="download-badge completed">✓ На сервере</span>