Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. URL'dan o'qish: oq ro'yxat va standart
- 2.1 Qaysi holat qayerda
- 2.2 Oq ro'yxat bilan o'qish
- 3. Yozish: push, replace va debounce
- 3.1 Qaysi o'zgarish — tarixda yozuv?
- 3.2 Debounce: yozish tugaganda
- 4. Sahifalash URL'da
- 4.1 ?sahifa= — son, chegara ichida
- 5. nuqs — URL holati uchun kutubxona
- 6. Vazifalar qadami: filtr va saralash router'ga (holat 09)
- 6.1 Kanon o'lchovi: ikki ega — bir tarix
- 6.2 marshrut.ts — faqat sof qism
- 6.3 useUrlTanlovi — router orqali
- 6.4 Natija
- 7. Hujumchi nigohi
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): URL yoki useState?
- 2-mashq (o'rta): standartni tozalang
- 3-mashq (qiyin): manzilni tuzatish
- 4-mashq: Vazifalar qadami
- 10. Real ishda
- Xulosa
- Manbalar
URL holat sifatida: filtr, saralash va sahifalash — useSearchParams, debounce, nuqs
Qisqacha: Ulashsa bo'ladigan holat — filtr, saralash, qidiruv, sahifa raqami — URL'ning so'rov qismida (
?filtr=faol&sahifa=2) turadi. Qoidalar: qiymat URL'dan oq ro'yxat bilan o'qiladi (begona qiymat — standart); standart qiymat URL'ga yozilmaydi; o'zgarmagan qiymat navigatsiya qilmaydi; tugma bosish —push(tarixda yozuv), har harfda yoziladigan qidiruv —replaceva debounce. Tarixning egasi bitta bo'lsin: router bor joyda o'zhistory.pushStateingizni yozmang. Kutubxona —nuqs(useQueryStateva parser'lar).
Bu darsda
- Qaysi holat URL'ga, qaysi biri
useStatega borishini tanlaysiz. - URL'dan qiymatni oq ro'yxat bilan o'qiysiz va standart qiymatni URL'dan tozalaysiz.
- Qidiruvni debounce bilan,
replaceorqali URL'ga yozasiz. - Sahifalashni URL'da qurasiz:
?sahifa=ni tekshirasiz va chegaralaysiz. nuqskutubxonasi nima berishini bilasiz vavazifalarfiltrini o'zpushStatedan React Router'ga ko'chirasiz.
Oldin bilishingiz kerak: React Router III: URL holati, URL va URLSearchParams, useSyncExternalStore.
1. Nega bu kerak?
04-darsda useSearchParams bilan tanishdik va qoida chiqardik: "havola bilan birga ketishi kerak bo'lgan holat — URL'da". Bugun shu qoidani chuqurlashtiramiz, chunki "URL'ga yozish" ning o'z tuzoqlari bor.
«Bahor» menyusida uchta tanlov bor: taom turi, narx bo'yicha saralash, sahifa. Jasur aka uchta shikoyat aytdi:
- "Do'stimga «ichimliklar, arzonidan, 2-sahifa» havolasini yubordim — u boshqa narsani ko'rdi." Bir tanlov URL'da, ikkitasi
useStateda edi. - "Qidiruvga «choy» yozdim, keyin «Orqaga» ni to'rt marta bosdim." Har harf — tarixda alohida yozuv.
- "Havolada
?sahifa=999— sahifa bo'sh, xato ham yo'q." URL'dagi qiymat tekshirilmagan.
vazifalar da yana bittasi bor, u ko'zga ko'rinmaydi. 17-qismdan beri filtr URL'ga o'zimiz yozgan history.pushState bilan tushadi. Router esa tarixni o'zi sanaydi. Ikki ega bitta tarixni boshqarsa nima bo'lishini kanon o'lchadi — «Vazifalar qadami» da.
Hayotiy o'xshatish: URL — buyurtma varaqasi. Ofitsiantning boshidagi eslatma (useState) boshqa ofitsiantga o'tmaydi. Varaqaga yozilgan narsa esa kimning qo'liga tushsa ham bir xil tayyorlanadi. Lekin varaqani mehmon ham to'ldirishi mumkin — oshpaz "pitsa" deb yozilganini tekshirmasdan pishirmaydi.
2. URL'dan o'qish: oq ro'yxat va standart
2.1 Qaysi holat qayerda
| Holat | Joyi | Nega |
|---|---|---|
| filtr, saralash, qidiruv, sahifa | URL | ulashiladi, yangilanishda va «Orqaga» da qoladi |
| ochiq tab, tanlangan bo'lim | URL (ko'pincha) | havola aniq joyga olib borsin |
| forma qoralamasi, input matni | useState |
yozish jarayoni — ulashilmaydi |
| ochiq menyu, hover, modal | useState |
vaqtinchalik UI |
| serverdan kelgan ro'yxat | server kesh | Server state vs client state |
Chegaraviy holatlar ham bor. Qidiruv maydonidagi matn — ham useState (har harf), ham URL (debounce bilan). Modal ba'zan URL'da: "rasmni kattalashtirib ko'rsatish" havolasini ulashish uchun (/taom/osh?rasm=2).
2.2 Oq ro'yxat bilan o'qish
URL — foydalanuvchi kiritgan ma'lumot. searchParams.get("turi") ning qiymati "issiq" ham, "pitsa" ham, "<script>" ham bo'lishi mumkin. Shuning uchun o'qish oq ro'yxat (allowlist) bilan: ruxsat etilgan qiymatlardan biri bo'lsa — o'zi, bo'lmasa — standart.
import {
createMemoryRouter,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
const TYPES = ["hammasi", "issiq", "ichimlik"];
const MENU = [
{ name: "Osh", type: "issiq" },
{ name: "Ko'k choy", type: "ichimlik" },
];
// Oq ro'yxat: yo'q yoki begona — standart
function readParam(params, name, allowed, fallback) {
const value = params.get(name);
return allowed.find((a) => a === value) ?? fallback;
}
// Standart qiymat URL'ga yozilmaydi
function setParam(params, name, value, fallback) {
const next = new URLSearchParams(params);
if (value === fallback) next.delete(name);
else next.set(name, value);
return next;
}
function Menu() {
const { search } = useLocation();
const [params, setParams] = useSearchParams();
const type = readParam(params, "turi", TYPES, "hammasi");
const shown = MENU.filter(
(d) => type === "hammasi" || d.type === type,
);
const choose = (value) =>
setParams(setParam(params, "turi", value, "hammasi"));
return (
<section>
<p>URL: {search || "(bo'sh)"}</p>
<button onClick={() => choose("hammasi")}>Hammasi</button>{" "}
<button onClick={() => choose("ichimlik")}>Ichimliklar</button>
<ul>
{shown.map((d) => (
<li key={d.name}>{d.name}</li>
))}
</ul>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu", element: <Menu /> }],
{ initialEntries: ["/menyu?turi=pitsa&til=uz"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
URL: ?til=uz
Hammasi Ichimliklar
Osh
Ko'k choyUch narsani kuzating:
- Ilova
?turi=pitsabilan ochildi — yiqilmadi, bo'sh ro'yxat ham bermadi:readParam"pitsa" ni tanimadi va "hammasi" ni qaytardi. - «Hammasi» bosilganda
turiURL'dan o'chdi.?turi=hammasiva?— bir xil sahifa. Bitta sahifa — bitta manzil: havola qisqa, Google ham bitta sahifa deb biladi. til=uz— biz tegmagan parametr — joyida qoldi.setParameski parametrlardan nusxa olib, faqat bittasini o'zgartiradi (04-darsdagi "funksiya bilan yozish" ning sof varianti).
readParam va setParam — sof funksiyalar: router'siz, brauzersiz node:test da tekshiriladi. vazifalar dagi parametrniOl va parametrniQoy aynan shunday.
Tekshirib ko'ring: Ilova
?turi=ichimlikbilan ochildi va foydalanuvchi «Ichimliklar» ni yana bosdi. Tarixga yangi yozuv qo'shiladimi?
Javob
Bu misolda — ha: setParams URL o'zgarmasa ham navigatsiya qiladi va tarixga yozuv qo'shadi. «Orqaga» bosilganda hech narsa o'zgarmaydi — foydalanuvchi "tugma ishlamayapti" deb o'ylaydi. Davosi — yozishdan oldin solishtirish: if (next.toString() !== params.toString()). vazifalar dagi useUrlTanlovi shunday qiladi — «Vazifalar qadami» da.
3. Yozish: push, replace va debounce
3.1 Qaysi o'zgarish — tarixda yozuv?
| O'zgarish | Usul | Nega |
|---|---|---|
| filtr, saralash, sahifa tugmasi | push (sukut) |
har bosish — foydalanuvchi qadami, «Orqaga» uni qaytarsin |
| qidiruvga yozish | replace |
har harf — qadam emas |
noto'g'ri qiymatni tuzatish (?sahifa=999 → ?sahifa=5) |
replace |
yo'naltirish — 04-dars qoidasi |
3.2 Debounce: yozish tugaganda
replace tarixni saqlaydi, lekin har harfda baribir navigatsiya bo'ladi: router hamma useSearchParams ni qayta chizadi, loader bo'lsa — serverga so'rov. "choy" — to'rt so'rov. Debounce — "foydalanuvchi yozishdan to'xtasin, keyin bir marta". Input useState da (har harf darhol ko'rinadi), URL'ga esa 300 ms jimlikdan keyin yoziladi:
import { useEffect, useState } from "react";
import {
createMemoryRouter,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
function Search() {
const { search } = useLocation();
const [params, setParams] = useSearchParams();
// Input — darhol (useState), URL — keyinroq
const [text, setText] = useState(params.get("q") ?? "");
useEffect(() => {
const timer = setTimeout(() => {
const next = new URLSearchParams(params);
if (text === "") next.delete("q");
else next.set("q", text);
if (next.toString() !== params.toString()) {
console.log("URL'ga yozildi:", text);
setParams(next, { replace: true });
}
}, 300);
// yangi harf — eski taymer bekor
return () => clearTimeout(timer);
}, [text, params, setParams]);
return (
<section>
<input
aria-label="Qidiruv"
value={text}
onChange={(e) => setText(e.target.value)}
/>
<p>URL: {search || "(bo'sh)"}</p>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu", element: <Search /> }],
{ initialEntries: ["/menyu"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Konsolda:
URL'ga yozildi: choyEkranda:
URL: ?q=choyYozilgan zahoti URL hali bo'sh edi (ekran qadami tekshirdi). 300 ms dan keyin — bitta yozuv, replace bilan. Har yangi harf oldingi taymerni bekor qiladi (effekt tozalash) — tez yozilgan "choy" bitta navigatsiya bo'ladi.
Maslahat: Qachon debounce kerak emas? Tugma va
select— bitta bosish, bitta qiymat. Debounce faqat "ketma-ket ko'p o'zgarish" uchun: matn, surgich (input type="range"). 300 ms — odatiy tanlov: odam yozayotganda harflar orasidagi pauza undan qisqa.
4. Sahifalash URL'da
4.1 ?sahifa= — son, chegara ichida
Sahifa raqami — URL holatining eng ko'p uchraydigan turi. U son, shuning uchun tekshiruv uch qadamli: songa aylantirish, butun sonmi, chegara ichidami. 999-sahifa so'ralsa, eng oxirgisini ko'rsatish mantiqiy:
import {
createMemoryRouter,
Link,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
const DISHES = ["Osh", "Manti", "Lag'mon", "Somsa", "Chuchvara"];
const SIZE = 2;
function readPage(params, last) {
const n = Number(params.get("sahifa") ?? "1");
if (!Number.isInteger(n) || n < 1) return 1;
return Math.min(n, last);
}
function pageHref(params, page) {
const next = new URLSearchParams(params);
if (page === 1) next.delete("sahifa");
else next.set("sahifa", String(page));
const s = next.toString();
return s === "" ? "?" : `?${s}`;
}
function Menu() {
const { search } = useLocation();
const [params] = useSearchParams();
const last = Math.ceil(DISHES.length / SIZE);
const page = readPage(params, last);
const items = DISHES.slice((page - 1) * SIZE, page * SIZE);
return (
<section>
<p>URL: {search || "(bo'sh)"}</p>
<ul>
{items.map((d) => (
<li key={d}>{d}</li>
))}
</ul>
<nav aria-label="Sahifalar">
{page > 1 && (
<Link to={pageHref(params, page - 1)}>‹ Oldingi</Link>
)}{" "}
{page}-sahifa / {last}{" "}
{page < last && (
<Link to={pageHref(params, page + 1)}>Keyingi ›</Link>
)}
</nav>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu", element: <Menu /> }],
{ initialEntries: ["/menyu?sahifa=999&turi=issiq"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
URL: ?sahifa=2&turi=issiq
Lag'mon
Somsa
‹ Oldingi 2-sahifa / 3 Keyingi ›?sahifa=999 — oxirgi, 3-sahifa (yiqilmadi, bo'sh ham emas). «‹ Oldingi» — ?sahifa=2, turi=issiq saqlandi. Bir necha detal:
- Sahifa tugmasi —
<Link>,<button>emas. Har sahifa — haqiqiy manzil: yangi tabda ochish mumkin, Google keyingi sahifalarni topadi (Link yoki navigate jadvali). - 1-sahifa URL'da yo'q — standart qiymat tozalanadi.
<nav aria-label="Sahifalar">— Malika (ekran o'quvchi) sahifalash blokini nomi bilan topadi.- Yaxshiroq variant —
?sahifa=999ni ko'rgandareplacebilan?sahifa=3ga yo'naltirish, toki manzil ham to'g'ri bo'lsin. Bu — 3-mashq.
Sahifadagi ma'lumot serverdan kelsa, sahifa loader'ga ham boradi: new URL(request.url).searchParams (data mode). Sahifalar orasida eski ma'lumotni ushlab turish va "yana yuklash" — TanStack Query IV darsida.
5. nuqs — URL holati uchun kutubxona
readParam, setParam, debounce, standart qiymat, tur — har loyihada qayta yoziladigan kod. nuqs uni bitta hook'ga yig'adi: useQueryState — useState ga o'xshaydi, lekin qiymat URL'da.
import {
parseAsInteger,
parseAsStringLiteral,
useQueryState,
} from "nuqs";
const TURLAR = ["hammasi", "issiq", "ichimlik"] as const;
function Menyu() {
// ?sahifa= — son; yo'q yoki yaroqsiz — 1; 1 bo'lsa URL'dan
// o'chadi
const [sahifa, setSahifa] = useQueryState(
"sahifa",
parseAsInteger.withDefault(1),
);
// ?turi= — faqat ro'yxatdagilar (oq ro'yxat)
const [turi, setTuri] = useQueryState(
"turi",
parseAsStringLiteral(TURLAR).withDefault("hammasi"),
);
// ?q= — har harfda emas: replace + kechiktirish
const [q, setQ] = useQueryState("q", {
defaultValue: "",
history: "replace",
});
// …
}Bugungi darsning hamma qoidasi bir joyda:
| Qoida | nuqs da |
|---|---|
| oq ro'yxat, tur | parser'lar: parseAsInteger, parseAsStringLiteral, parseAsBoolean… |
| standart qiymat tozalanadi | .withDefault(…) — qiymat standartga teng bo'lsa URL'dan o'chadi |
| push yoki replace | history: "push" / "replace" |
| bir nechta parametr bitta yozuvda | useQueryStates({ … }) |
| debounce | yozishni kechiktirish sozlamasi bor (hujjatdan tekshiring) |
nuqs router'ga adapter orqali ulanadi: ilova ildizini <NuqsAdapter> bilan o'raysiz. Next.js, TanStack Router va React Router'ning har versiyasi uchun alohida adapter bor. 2026-10-14 holatiga nuqs 2.10.2 (2026-10-08 da chiqqan) React Router 8 ni rasman qo'llaydi: peerDependencies da "react-router": "^5 || ^6 || ^7 || ^8", adapter — nuqs/adapters/react-router/v8 (npm view nuqs bilan tekshirildi).
Diqqat:
nuqskanonda o'lchanmagan va natija oynasida yo'q — yuqoridagi blok tekshirilmagan, to'plam hajmi ham o'lchanmagan.vazifalarda ikki parametr bor va ikkita sof funksiya yetarli — kutubxona qo'shilmadi.
6. Vazifalar qadami: filtr va saralash router'ga (holat 09)
6.1 Kanon o'lchovi: ikki ega — bir tarix
17-qismdan beri vazifalar filtri URL'ga o'zimizning history.pushState(null, "", url) bilan yoziladi (useSyncExternalStore darsi). 19/#02 dan esa sahifalarni React Router boshqaradi. Router har tarix yozuviga o'z ma'lumotini qo'yadi: { usr, key, idx } (idx — tarixdagi o'rni). Bizning pushState(null) yozuvlarida bu ma'lumot yo'q.
Kanon Chrome 155 da besh qadamni bajarib, har qadamda history.state ni yozib oldi (natijalar/09-tarix-oldin.txt, aynan):
ochildi / history.state={"idx":0}
filtr «Faol» bosildi /?filtr=faol history.state=null
saralash «alifbo» /?filtr=faol&saralash=alifbo history.state=null
«Batafsil» (router) /vazifa/1 history.state={"usr":null,"key":"09dtgdck","idx":1}
Orqaga (popstate) /?filtr=faol&saralash=alifbo history.state=nullTo'rtinchi qatorga qarang: «Batafsil» — aslida tarixdagi to'rtinchi yozuv (idx: 3 bo'lishi kerak). Router esa idx: 1 deb hisobladi — filtr va saralash yozuvlarini u ko'rmagan. Beshinchi qator: «Orqaga» dan keyin state=null — router bu yozuvni tanimaydi, unda key yo'q.
flowchart LR
A["/ <br/>idx 0"] --> B["?filtr=faol<br/>state: null"]
B --> C["…&saralash=alifbo<br/>state: null"]
C --> D["/vazifa/1<br/>router: idx 1"]
D -. "aslida 4-yozuv" .-> X["idx 3 bo'lishi kerak"]Hozircha ekranda xato ko'rinmaydi. Lekin 04-darsdagi «Orqaga» tugmasi key va navigatsiya turiga tayanadi; key siz yozuvlar — vaqtli bomba. Qoida: tarixning egasi bitta bo'lsin. Router bor joyda URL'ni faqat router orqali o'zgartiring.
git switch -c refactor/url-router6.2 marshrut.ts — faqat sof qism
O'z pushState imiz, obunachilar ro'yxati va popstate tinglovchisi o'chirildi. Faylda faqat o'qish va yangi parametrlar yasash qoldi (kanondan aynan, izoh qisqartirilgan):
/** To'liq manzil (testlar) yoki router bergan parametrlar */
type Manba = string | URLSearchParams;
const parametrlar = (manba: Manba): URLSearchParams =>
typeof manba === "string" ? new URL(manba).searchParams : manba;
// URL'dagi qiymat — oq ro'yxatdan; yo'q yoki begona bo'lsa —
// standart
function parametrniOl<T extends string>(
manba: Manba,
nomi: string,
ruxsatlar: readonly T[],
standart: T,
): T {
const qiymat = parametrlar(manba).get(nomi);
return ruxsatlar.find((ruxsat) => ruxsat === qiymat) ?? standart;
}
/**
* Yangi parametrlar (eskisi o'zgarmaydi): standart qiymat URL'ga
* yozilmaydi, boshqa parametrlar (muvaffaqiyatsiz=1...) joyida qoladi
*/
export function parametrniQoy(
eski: URLSearchParams,
nomi: string,
qiymat: string,
standart: string,
): URLSearchParams {
const yangi = new URLSearchParams(eski);
if (qiymat === standart) {
yangi.delete(nomi);
} else {
yangi.set(nomi, qiymat);
}
return yangi;
}«Oq ro'yxat bilan o'qish» bo'limidagi readParam/setParam ning TypeScript varianti. parametrniOl<T extends string> — generic: ruxsatlar Filtr[] bo'lsa, natija ham Filtr. find ning natijasi — T | undefined, ?? standart uni T qiladi.
6.3 useUrlTanlovi — router orqali
Hook endi useSyncExternalStore o'rniga useSearchParams ni ishlatadi (kanondan aynan):
export function useUrlTanlovi() {
const [parametrlar, setParametrlar] = useSearchParams();
// Yozuv await'dan keyin ham bo'lishi mumkin (vazifa qo'shildi —
// filtr "hammasi" ga): render paytidagi parametrlar eskirgan
// bo'ladi. Eng so'nggisi — ref'da (commit'dan keyin yoziladi)
const songgi = useRef(parametrlar);
useLayoutEffect(() => {
songgi.current = parametrlar;
});
// URL o'zgarmasa — navigatsiya (va tarix yozuvi) yo'q
function yoz(nomi: string, qiymat: string, standart: string) {
const eski = songgi.current;
const yangi = parametrniQoy(eski, nomi, qiymat, standart);
if (yangi.toString() !== eski.toString()) {
songgi.current = yangi;
// preventScrollReset: filtr — o'sha sahifa, aylantirilgan joy
// qolsin
void setParametrlar(yangi, { preventScrollReset: true });
}
}
return {
filtr: filtrniOl(parametrlar),
saralash: saralashniOl(parametrlar),
filtrniYoz: (filtr: Filtr) => yoz("filtr", filtr, "hammasi"),
saralashniYoz: (saralash: Saralash) =>
yoz("saralash", saralash, "standart"),
};
}Uch nozik joy:
yangi.toString() !== eski.toString()— «Tekshirib ko'ring» dagi savolning javobi: o'sha filtr qayta bosilsa, tarix o'smaydi.songgiref. Vazifa qo'shilgach,awaitdan keyin kod filtrni "hammasi" ga qaytaradi. Shu paytgacha foydalanuvchi saralashni o'zgartirgan bo'lishi mumkin. Render paytidagiparametrlar— eski nusxa (state — snapshot). Ref har commit'dan keyin yangilanadi va yozuv eng so'nggi parametrlardan boshlanadi.preventScrollReset: true— filtr — o'sha sahifa. Usiz router uzun ro'yxatni tepaga sakratishi mumkin edi (aylantirish — keyingi darsda).
App.tsx da bitta o'zgarish: filtrniYoz, saralashniYoz endi hook'dan keladi.
6.4 Natija
Xuddi o'sha besh qadam, tuzatishdan keyin (natijalar/09-tarix-keyin.txt, aynan):
ochildi / history.state={"idx":0}
filtr «Faol» bosildi /?filtr=faol history.state={"usr":null,"key":"urthxv9q","idx":1}
saralash «alifbo» /?filtr=faol&saralash=alifbo history.state={"usr":null,"key":"cdn42fo1","idx":2}
«Batafsil» (router) /vazifa/1 history.state={"usr":null,"key":"fuwaevq4","idx":3}
Orqaga (popstate) /?filtr=faol&saralash=alifbo history.state={"usr":null,"key":"cdn42fo1","idx":2}idx — 0, 1, 2, 3: router hisobi brauzer tarixi bilan bir xil. «Orqaga» dan keyin router o'sha kalitni (cdn42fo1) taniydi.
git add -A
git commitCommit (kanon): refactor: filtr va saralash — useSearchParams (tarixning egasi — router).
npm test— node:test 181 (o'zpushStatetestlariparametrniQoytestlariga almashdi; 04-dars tuzatishi bilan), Vitest 56. Vitest'daAppendiBrowserRouterichida chiziladi — testlarlocation.searchni tekshiradi.- Brauzer: 159/159, 72/72, 19-qism — 52/52. Yangilaridan (aynan):
✅ o'sha filtr qayta bosildi — tarix o'smadi: [0,2]
✅ «Batafsil»: idx 3 (router hisobi tarix bilan bir xil): 3
✅ Orqaga ×2: filtr faol, saralash standart, ro'yxat ergashdi: [["?filtr=faol",1,"string"],"standart",["1","3"]]Diff: 5 fayl, +114 −118 — kod qisqardi. Sahifalash vazifalar da yo'q: ro'yxat virtual (18-qism), API'da sahifa tushunchasi yo'q.
7. Hujumchi nigohi
URL'dagi qiymat — hujumchi to'liq boshqaradigan kirish. Uch xavf:
- Tekshirilmagan qiymat — ilova ichiga.
?saralash=__proto__,?turi=<img onerror=…>. Oq ro'yxat (parametrniOl) faqat ma'lum qiymatlarni o'tkazadi — qolgani standart. React matnni escape qiladi, lekin qiymatdangerouslySetInnerHTML,href,styleyoki obyekt kaliti (obj[saralash]) ga tushsa — xavf (Frontend xavfsizligi). - Server so'roviga to'g'ridan.
fetch(\/api/taom?saralash=${q}`)—qda&narx=0bo'lsa, so'rovga yangi parametr qo'shiladi. Har doimURLSearchParams` bilan yasang — u qiymatni kodlaydi. - Resurs hujumi.
?hajm=1000000— "bir sahifada million taom". Son parametrlarni chegaralang (Math.min), server ham chegaralaydi.
8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
?turi=pitsa — bo'sh ro'yxat |
qiymat tekshirilmagan | oq ro'yxat, begona — standart |
| filtr bosilsa, qidiruv yo'qoldi | setSearchParams({ … }) hammasini almashtirdi |
eski parametrlardan nusxa (new URLSearchParams(eski)) |
?filtr=hammasi va ? — ikki manzil |
standart qiymat yozildi | standartga teng — delete |
| «Orqaga» ni ko'p bosish kerak | har harf — push |
replace + debounce |
| o'sha tugma qayta bosilsa, «Orqaga» "ishlamaydi" | URL o'zgarmasa ham navigatsiya | toString() solishtiruvi |
?sahifa=abc → NaN, ?sahifa=999 → bo'sh |
son tekshirilmagan | Number.isInteger, Math.min(n, last) |
router tarixi adashdi (state=null) |
o'z history.pushState |
URL'ni faqat router orqali o'zgartiring |
| filtrdan keyin sahifa tepaga sakradi | aylantirish tiklandi | preventScrollReset: true |
9. Mashqlar
Mashqlar kurs/mashqlar/19/09-url-holat/ da. Har yechimni «Ishga tushir» bilan sinang.
1-mashq (oson): URL yoki useState?
Har holat uchun joyni yozing (URL yoki useState):
- Menyuda tanlangan saralash («arzonidan») →
- "Buyurtma berish" modalining ochiq/yopiqligi →
- Qidiruv maydoniga hozir yozilayotgan harflar →
- Buyurtmalar ro'yxatining 3-sahifasi →
Yechim
Birinchi va to'rtinchi — URL: havolani ulashganda boshqa odam ham aynan shuni ko'rishi kerak. Ikkinchisi — useState: modal vaqtinchalik, havolada ochiq modal kutilmaydi. Uchinchisi — useState, lekin debounce bilan URL'ga ham ketadi: yozilayotgan har harf — useState, tugagan qidiruv — URL. Savol doim bitta: "Bu havola bilan birga ketishi kerakmi?"
2-mashq (o'rta): standartni tozalang
Quyidagi saralash tugmalari standart qiymatni ham URL'ga yozadi: «Standart» bosilsa — ?saralash=standart. Tuzating: standart bo'lsa, saralash URL'dan o'chsin. turi saqlansin.
Ishora: «Oq ro'yxat bilan o'qish» bo'limidagi setParam.
import {
createMemoryRouter,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
function Sort() {
const { search } = useLocation();
const [params, setParams] = useSearchParams();
function choose(value) {
const next = new URLSearchParams(params);
next.set("saralash", value);
setParams(next);
}
return (
<section>
<p>URL: {search || "(bo'sh)"}</p>
<button onClick={() => choose("standart")}>
Standart
</button>{" "}
<button onClick={() => choose("arzon")}>Arzonidan</button>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu", element: <Sort /> }],
{ initialEntries: ["/menyu?turi=issiq"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
URL: ?turi=issiq&saralash=standart
Standart ArzonidanYechim
import {
createMemoryRouter,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
function Sort() {
const { search } = useLocation();
const [params, setParams] = useSearchParams();
function choose(value) {
const next = new URLSearchParams(params);
if (value === "standart") next.delete("saralash");
else next.set("saralash", value);
if (next.toString() !== params.toString()) setParams(next);
}
return (
<section>
<p>URL: {search || "(bo'sh)"}</p>
<button onClick={() => choose("standart")}>
Standart
</button>{" "}
<button onClick={() => choose("arzon")}>Arzonidan</button>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu", element: <Sort /> }],
{ initialEntries: ["/menyu?turi=issiq"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
URL: ?turi=issiq
Standart ArzonidanOraliq holatda ?turi=issiq&saralash=arzon, oxirida — yana ?turi=issiq: ochilgandagi manzil bilan bir xil. Qo'shimcha: toString() solishtiruvi — «Standart» ikki marta bosilsa, tarix o'smaydi.
3-mashq (qiyin): manzilni tuzatish
«Sahifalash URL'da» bo'limidagi misolni o'zgartiring: ?sahifa= yaroqsiz yoki chegaradan tashqarida bo'lsa (999, abc, 0), komponent to'g'ri sahifani ko'rsatish bilan birga manzilni ham tuzatsin — replace bilan. 1-sahifa bo'lsa, sahifa URL'dan o'chsin.
Ishora: 04-darsdagi <Navigate replace> — kanonik manzil.
Yechim
import {
createMemoryRouter,
Navigate,
RouterProvider,
useLocation,
useSearchParams,
} from "react-router";
const DISHES = ["Osh", "Manti", "Lag'mon", "Somsa", "Chuchvara"];
const SIZE = 2;
function readPage(params, last) {
const n = Number(params.get("sahifa") ?? "1");
if (!Number.isInteger(n) || n < 1) return 1;
return Math.min(n, last);
}
function Menu() {
const { pathname, search } = useLocation();
const [params] = useSearchParams();
const last = Math.ceil(DISHES.length / SIZE);
const page = readPage(params, last);
// Kanonik qiymat: 1 — yo'q, boshqasi — satr
const wanted = page === 1 ? null : String(page);
if (params.get("sahifa") !== wanted) {
const next = new URLSearchParams(params);
if (wanted === null) next.delete("sahifa");
else next.set("sahifa", wanted);
const s = next.toString();
return <Navigate to={pathname + (s ? `?${s}` : "")} replace />;
}
const items = DISHES.slice((page - 1) * SIZE, page * SIZE);
return (
<section>
<p>URL: {search || "(bo'sh)"}</p>
<p>
{page}-sahifa: {items.join(", ")}
</p>
</section>
);
}
const router = createMemoryRouter(
[{ path: "/menyu", element: <Menu /> }],
{ initialEntries: ["/menyu?sahifa=999&turi=issiq"] },
);
export default function App() {
return <RouterProvider router={router} />;
}Ekranda:
URL: ?sahifa=3&turi=issiq
3-sahifa: Chuchvaraparams.get("sahifa") URL'dagi xom qiymat, wanted — bo'lishi kerak bo'lgani. Farq qilsa — <Navigate replace>: tarix o'smaydi, «Orqaga» ?sahifa=999 ga qaytarmaydi. initialEntries ni ?sahifa=abc yoki ?sahifa=1 ga almashtirib sinang — ikkalasida ham URL ?turi=issiq bo'ladi.
4-mashq: Vazifalar qadami
refactor/url-router branch'ida «Vazifalar qadami» dagi o'zgarishlarni qiling: marshrut.ts — sof funksiyalar, useUrlTanlovi — useSearchParams, App.tsx — yozish hook'dan, testlar (parametrniQoy, Vitest'da BrowserRouter). Keyin commit.
Yechim
Tekshiruv ro'yxati (npm run dev, DevTools konsolida history.state):
- «Faol» — manzil
?filtr=faol,history.statedakeyvaidx: 1. - «Faol» ni yana bosing —
history.lengtho'zgarmaydi. - «Hammasi» —
filtrURL'dan o'chadi. - Filtr, saralash, «Batafsil», keyin «Orqaga» ikki marta — avval saralash, keyin filtr qaytadi, ro'yxat ergashadi.
- Uzun ro'yxatni pastga aylantirib, filtr bosing — sahifa tepaga sakramaydi.
marshrut.tsdahistory.pushStateqolmagan:grep pushState src— bo'sh.npm test— node 181, Vitest 56.
10. Real ishda
- Internet-do'konlar — Uzum, Ozon, Amazon filtrlari URL'da:
?narx=0-100000&rang=qizil&sahifa=2. Filtrlangan sahifani ulashish va Google indeksi shunga tayanadi. - Admin jadvallar —
?saralash=-sana&sahifa=3&hajm=50. TanStack Table + URL holati — ko'p uchraydigan juftlik. nuqs— Next.js va React Router loyihalarida keng tarqalgan; TanStack Router'davalidateSearch(oldingi dars) shu vazifani bajaradi.- Intervyu savollari: "filtrni state'da yoki URL'da saqlaysizmi va nega?", "qidiruvda har harf tarixga yozilmasligi uchun nima qilasiz?", "URL'dagi qiymatga ishonsa bo'ladimi?".
Xulosa
- Ulashiladigan holat (filtr, saralash, qidiruv, sahifa) — URL'da; vaqtinchalik UI —
useState. - O'qish — oq ro'yxat bilan, begona qiymat — standart. Son —
Number.isIntegerva chegara. - Yozish: standart qiymat URL'dan o'chadi, boshqa parametrlar saqlanadi, o'zgarmagan URL navigatsiya qilmaydi.
- Tugma —
push, yozish —replace+ debounce (300 ms). - Tarixning egasi bitta: router bor joyda o'z
pushStateyo'q (kanon:state=null,idxadashdi). nuqs— parser'lar,withDefault,historysozlamasi bilan shu qoidalarni bitta hook'ga yig'adi.
Keyingi dars: Scroll, breadcrumb va sahifa o'tishlari — sahifa almashganda aylantirish va fokus qayerda bo'ladi, non yo'li marshrutlardan qanday quriladi va View Transitions bilan silliq o'tish.
Manbalar
- React Router:
useSearchParams(preventScrollReset,replace) — reactrouter.com/api/hooks/useSearchParams (2026-10 holatiga, v8.4.0) - nuqs: Basic usage, Parsers, Options, Adapters — nuqs.dev;
npm view nuqs version peerDependencies exports(2.10.2, 2026-10-14) - MDN:
History.state,History.pushState(),URLSearchParams— developer.mozilla.org
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!