diff --git a/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx b/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx
index 038529a..d36d5b0 100644
--- a/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx
+++ b/src/pages/stocks/ui/StocksPage/StocksPage.test.tsx
@@ -10,6 +10,19 @@ import { createTestQueryClient } from "@/test/query";
import { server } from "@/test/server";
import { StocksPage } from "./StocksPage";
+import { useStocksPageVm } from "./StocksPage.vm";
+
+const StocksPageVmProbe = () => {
+ const vm = useStocksPageVm();
+
+ return (
+ <>
+
+
+
+ >
+ );
+};
afterEach(() => vi.useRealTimers());
@@ -159,6 +172,70 @@ describe("stock search", () => {
);
});
+ it("keeps a newer search when an earlier page request finishes late", async () => {
+ const oldPage = Promise.withResolvers();
+ let searches = 0;
+
+ server.use(
+ http.post("*/api/v1/stocks/screener/search", () => {
+ searches += 1;
+
+ return HttpResponse.json({
+ asOf: "2026-10-10T10:00:00Z",
+ hasNext: searches === 1,
+ items: [{
+ countryOfRisk: "RU",
+ currency: "RUB",
+ instrumentUid: searches === 1 ? "old" : "new",
+ isin: null,
+ logoName: null,
+ lot: 1,
+ name: searches === 1 ? "Старый" : "Новый",
+ realExchange: "REAL_EXCHANGE_MOEX",
+ sector: "financial",
+ shareType: "SHARE_TYPE_COMMON",
+ ticker: searches === 1 ? "OLD" : "NEW",
+ }],
+ page: 1,
+ searchId: searches === 1 ? "old-search" : "new-search",
+ });
+ }),
+ http.get("*/api/v1/stocks/screener/search/old-search", async () => {
+ await oldPage.promise;
+
+ return HttpResponse.json({
+ asOf: "2026-10-10T10:00:00Z",
+ hasNext: false,
+ items: [{
+ countryOfRisk: "RU",
+ currency: "RUB",
+ instrumentUid: "old-page",
+ isin: null,
+ logoName: null,
+ lot: 1,
+ name: "Старый результат",
+ realExchange: "REAL_EXCHANGE_MOEX",
+ sector: "financial",
+ shareType: "SHARE_TYPE_COMMON",
+ ticker: "OLD",
+ }],
+ page: 2,
+ searchId: "old-search",
+ });
+ }),
+ );
+ render();
+ fireEvent.click(screen.getByRole("button", { name: "search" }));
+ await screen.findByText("Старый");
+ fireEvent.click(screen.getByRole("button", { name: "page" }));
+ fireEvent.click(screen.getByRole("button", { name: "search" }));
+ await screen.findByText("Новый");
+ oldPage.resolve();
+
+ await waitFor(() => expect(screen.getByText("Новый")).toBeVisible());
+ expect(screen.queryByText("Старый результат")).not.toBeInTheDocument();
+ });
+
it("disables filters and shows loading while searching", async () => {
const { promise, resolve } = Promise.withResolvers();
server.use(
diff --git a/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts b/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts
index fe1de7a..8ce87d7 100644
--- a/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts
+++ b/src/pages/stocks/ui/StocksPage/StocksPage.vm.ts
@@ -83,17 +83,20 @@ function useStocksPageVm() {
const changePage = async (nextPage: number) => {
if (!page) return;
+ const currentRequestId = requestIdRef.current + 1;
+ requestIdRef.current = currentRequestId;
setPending(true);
setError(false);
try {
- setPage(await readPage(page.searchId, nextPage));
+ const result = await readPage(page.searchId, nextPage);
+ if (requestIdRef.current === currentRequestId) setPage(result);
}
catch {
- setError(true);
+ if (requestIdRef.current === currentRequestId) setError(true);
}
finally {
- setPending(false);
+ if (requestIdRef.current === currentRequestId) setPending(false);
}
};