From 10c16ba2cbcfce5b569ada08d1f24c3d5f843b0f Mon Sep 17 00:00:00 2001 From: vrubelroman Date: Wed, 16 Sep 2026 19:37:33 +0000 Subject: [PATCH] =?UTF-8?q?Fix=20DownloadButton=20reverting=20to=20"=D0=A1?= =?UTF-8?q?=D0=BA=D0=B0=D1=87=D0=B0=D1=82=D1=8C"=20after=20completion?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- frontend/src/components/DownloadButton.tsx | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/frontend/src/components/DownloadButton.tsx b/frontend/src/components/DownloadButton.tsx index 7505738..964ca1a 100644 --- a/frontend/src/components/DownloadButton.tsx +++ b/frontend/src/components/DownloadButton.tsx @@ -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 ✓ На сервере