fix(tradein/v2): гонка при клике по строке «Предыдущие оценки» — отчёт не загружался #2428

Merged
lekss361 merged 4 commits from fix/tradein-cacheview-restore-race into main 2026-07-04 14:47:02 +00:00
Showing only changes of commit 10f4f6adac - Show all commits

View file

@ -410,11 +410,22 @@ export default function TradeInV2Page() {
// stale value is locked in forever (mirrors how `freshResult` already
// overrides the same race for the post-submit path below). Cleared once
// `restored.data` actually reflects this id (see effect below).
const [pendingRestoreId, setPendingRestoreId] = useState<string | null>(
null,
);
//
// Three states, not two: `undefined` means "no override, defer to
// readUrlId()"; `null` means "override to no-id" (handleNew's reset);
// a string means "override to this id" (a row was just clicked). Using
// plain `null` for both "no override" and "force no-id" was tried first and
// reintroduced this exact race for handleNew(): forcing pendingRestoreId
// from a bad id to `null` would, under `pendingRestoreId ?? readUrlId()`,
// fall through to readUrlId() on the very next render — which is still
// stale until router.replace("/v2") finishes — so the bad id came right
// back. The two intents need distinguishable values.
const [pendingRestoreId, setPendingRestoreId] = useState<
string | null | undefined
>(undefined);
const urlId = pendingRestoreId ?? readUrlId();
const urlId =
pendingRestoreId !== undefined ? pendingRestoreId : readUrlId();
// Post-mount flag: readUrlId() is null on SSR but a UUID on the client's first
// render, so any structure that depends on the id (e.g. the PDF <a> vs disabled
@ -486,11 +497,12 @@ export default function TradeInV2Page() {
// what the URL will become — rather than risk reverting to a stale read.
useEffect(() => {
if (
pendingRestoreId !== undefined &&
pendingRestoreId !== null &&
restored.data?.estimate_id === pendingRestoreId &&
readUrlId() === pendingRestoreId
) {
setPendingRestoreId(null);
setPendingRestoreId(undefined);
}
}, [pendingRestoreId, restored.data]);
@ -674,7 +686,8 @@ export default function TradeInV2Page() {
setFreshResult(est);
// A fresh submission always supersedes any pending restore-by-id
// override (#2424 fix) — drop it so it can't shadow a later restore.
setPendingRestoreId(null);
// `undefined` (not `null`): there is no override to force, just none.
setPendingRestoreId(undefined);
// A new estimate consumed quota and added a history row — refresh both
// so the TopNav badge + «Предыдущие оценки» overlay reflect it.
void queryClient.invalidateQueries({
@ -695,6 +708,9 @@ export default function TradeInV2Page() {
function handleNew() {
setFreshResult(null);
// `null`, not `undefined`: forces urlId to no-id immediately, synchronously
// clearing restoreActive/restoreError even before router.replace below
// finishes and readUrlId() would otherwise still read the stale id.
setPendingRestoreId(null);
router.replace("/v2", { scroll: false });
}