diff --git a/tradein-mvp/frontend/src/app/v2/page.tsx b/tradein-mvp/frontend/src/app/v2/page.tsx index 7a2b16f7..3f6868ac 100644 --- a/tradein-mvp/frontend/src/app/v2/page.tsx +++ b/tradein-mvp/frontend/src/app/v2/page.tsx @@ -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( - 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 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 }); }