fix(web): only announce a state change after the server confirms it
The 'Marked watched' / 'Hidden' notices (and resolving a stale notice on unwatch/unhide) fired synchronously, before the api.setState call resolved. When the write failed — e.g. the API was unreachable — the optimistic card override was rolled back in .catch, but the notice had already been emitted and lingered, falsely claiming a change that never persisted. Move all three branches into the .then() so the notice is produced only once the server has actually applied the change; on failure the card reverts and nothing is announced (the connection-lost status already explains why).
This commit is contained in:
parent
0ab6742b44
commit
9f9d2544ef
1 changed files with 36 additions and 30 deletions
|
|
@ -144,9 +144,44 @@ export default function Feed({
|
|||
(id: string, status: string) => {
|
||||
setOverrides((o) => ({ ...o, [id]: status }));
|
||||
// Refetch once the server has the change so other views (e.g. Hidden) are in sync.
|
||||
// Announce the change only AFTER the server confirms it: a "Marked watched"/"Hidden"
|
||||
// notice (or resolving a stale one) is a claim of success, so firing it optimistically
|
||||
// would falsely report a change that the .catch below is about to roll back (e.g. when
|
||||
// the API is unreachable).
|
||||
api
|
||||
.setState(id, status)
|
||||
.then(() => qc.invalidateQueries({ queryKey: ["feed"] }))
|
||||
.then(() => {
|
||||
qc.invalidateQueries({ queryKey: ["feed"] });
|
||||
if (status === "hidden") {
|
||||
const v = loadedRef.current.find((x) => x.id === id);
|
||||
notify({
|
||||
message: v?.title
|
||||
? i18n.t("feed.hiddenNamed", { title: v.title })
|
||||
: i18n.t("feed.hidden"),
|
||||
action: { label: i18n.t("feed.undo"), onClick: () => onState(id, "new") },
|
||||
meta: {
|
||||
kind: "video-hidden",
|
||||
videoId: id,
|
||||
title: v?.title ?? "this video",
|
||||
channelId: v?.channel_id ?? "",
|
||||
channelName: v?.channel_title ?? "",
|
||||
},
|
||||
});
|
||||
} else if (status === "watched") {
|
||||
const v = loadedRef.current.find((x) => x.id === id);
|
||||
notify({
|
||||
message: v?.title
|
||||
? i18n.t("feed.markedWatchedNamed", { title: v.title })
|
||||
: i18n.t("feed.markedWatched"),
|
||||
action: { label: i18n.t("feed.unwatch"), onClick: () => onState(id, "new") },
|
||||
meta: { kind: "video-watched", videoId: id, title: v?.title ?? "this video" },
|
||||
});
|
||||
} else if (status === "new") {
|
||||
// Unhide / unwatch (from a card, the toast's Undo, or the center): quietly resolve
|
||||
// any stale hide/watch notice for this video so it doesn't linger with a dead action.
|
||||
resolveVideo(id);
|
||||
}
|
||||
})
|
||||
.catch(() =>
|
||||
// Server rejected the change — drop the optimistic override so the card
|
||||
// reverts to the real (unchanged) state instead of staying phantom-hidden.
|
||||
|
|
@ -156,35 +191,6 @@ export default function Feed({
|
|||
return next;
|
||||
})
|
||||
);
|
||||
if (status === "hidden") {
|
||||
const v = loadedRef.current.find((x) => x.id === id);
|
||||
notify({
|
||||
message: v?.title
|
||||
? i18n.t("feed.hiddenNamed", { title: v.title })
|
||||
: i18n.t("feed.hidden"),
|
||||
action: { label: i18n.t("feed.undo"), onClick: () => onState(id, "new") },
|
||||
meta: {
|
||||
kind: "video-hidden",
|
||||
videoId: id,
|
||||
title: v?.title ?? "this video",
|
||||
channelId: v?.channel_id ?? "",
|
||||
channelName: v?.channel_title ?? "",
|
||||
},
|
||||
});
|
||||
} else if (status === "watched") {
|
||||
const v = loadedRef.current.find((x) => x.id === id);
|
||||
notify({
|
||||
message: v?.title
|
||||
? i18n.t("feed.markedWatchedNamed", { title: v.title })
|
||||
: i18n.t("feed.markedWatched"),
|
||||
action: { label: i18n.t("feed.unwatch"), onClick: () => onState(id, "new") },
|
||||
meta: { kind: "video-watched", videoId: id, title: v?.title ?? "this video" },
|
||||
});
|
||||
} else if (status === "new") {
|
||||
// Unhide / unwatch (from a card, the toast's Undo, or the center): quietly resolve any
|
||||
// stale hide/watch notice for this video so it doesn't linger with a dead action.
|
||||
resolveVideo(id);
|
||||
}
|
||||
},
|
||||
[qc]
|
||||
);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue