IlmHamroh
JavaScript Full-stack/17-qism. React asoslari19/45-dars21 daqiqa
Mundarija (32)

Formalar II: uncontrolled inputlar, FormData va React 19 form action

Qisqacha: Qiymat faqat yuborishda kerak bo'lsa, state shart emas: maydon uncontrolled qoladi (defaultValue, name), yuborilganda esa new FormData(form) bilan hammasi bir martada o'qiladi. Brauzerning o'z tekshiruvi (required, pattern, min) ham ishlaydi. React 19 da <form action={fn}> funksiyaga FormData ni o'zi beradi, useActionState esa natija va "kutilmoqda" holatini boshqaradi. Lekin action tugagach React formani o'zi tozalaydi — tekshiruv xato topsa ham. Shuning uchun vazifalar dagi yangi vazifa formasi onSubmit + FormData bilan yozilgan: biz buni o'lchab ko'rdik.

Bu darsda

  • Uncontrolled maydonni defaultValue va name bilan yozasiz.
  • FormData, Object.fromEntries va getAll bilan formani bir martada o'qiysiz.
  • Brauzer validatsiyasini React formasida ishlatasiz va validity ni o'qiysiz.
  • <form action> va useActionState ni yozasiz: natija, xato va isPending.
  • Action'dan keyingi avtomatik tozalash va form.reset() tuzoqlarini taniysiz.
  • vazifalar da yangi vazifa va import formalari FormData ga o'tadi, eksport va import React'ga ko'chadi.

Oldin bilishingiz kerak: Formalar I: controlled inputlar, FormData, Brauzer validatsiyasi.

1. Nega bu kerak?

Oldingi darsda har maydonni state'ga bog'ladik. Bu kuchli, lekin narxi bor: har maydonga useState, value, onChange. Har harfda qayta render. «Bahor» ning "Fikr bildiring" formasini ko'raylik: ism, telefon, baho, izoh. Unda yozilayotganda hech narsa hisoblanmaydi va tekshirilmaydi — forma yuborilganda bir marta o'qiladi va serverga ketadi. To'rtta useState bu yerda ortiqcha ish.

FormData darsida vanilla JavaScript'da shunday qilgan edik: maydonlarga name beramiz, submit da new FormData(form) hammasini yig'adi. Brauzer qiymatlarni o'zi saqlaydi — u bunga yaxshi. React bu yo'lni ham qo'llab-quvvatlaydi. React 19 esa unga yangi qulaylik qo'shdi — form action.

Hayotiy o'xshatish: anketa. Controlled forma — har javobni yozayotganingizda yoningizda turib, ovoz chiqarib takrorlab boradigan kotib. Uncontrolled forma — anketani to'ldirib, oxirida topshirasiz, kotib esa uni bir martada o'qiydi. Ko'p hollarda ikkinchisi yetadi.

2. Uncontrolled maydon

2.1 defaultValue va name

Uncontrolled input — qiymati DOM'da (brauzerda) saqlanadigan maydon. React unga faqat boshlang'ich qiymatni beradi:

jsx
import { useState } from "react";

function App() {
  const [sent, setSent] = useState(null);

  function handleSubmit(e) {
    e.preventDefault(); // sahifa qayta yuklanmasin
    const data = new FormData(e.currentTarget);
    setSent(data.get("name"));
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Ism <input name="name" defaultValue="" />
      </label>
      <button>Yuborish</button>
      <p>{sent === null ? "Hali yuborilmadi" : `Rahmat, ${sent}!`}</p>
    </form>
  );
}

Ekranda:

text
Ism Yuborish
Rahmat, Malika!

useState faqat natija uchun — maydon uchun emas. Malika yozayotganda React hech narsa qilmaydi, qayta render ham yo'q. Yuborilganda:

Qadam Maydon qiymati (DOM'da) sent Ekranda
boshida "" null Hali yuborilmadi
"Malika" yozildi "Malika" null Hali yuborilmadi (render yo'q)
«Yuborish» "Malika" "Malika" Rahmat, Malika!

defaultValue — maydon yaratilganda yoziladigan boshlang'ich qiymat. Keyin u maydonning o'ziniki: defaultValue ni o'zgartirsangiz ham, allaqachon yaratilgan maydon o'zgarmaydi. Checkbox va radio uchun — defaultChecked, select uchun ham defaultValue.

e.currentTarget — ishlovchi ulangan element, ya'ni <form>. e.target emas: ichki elementdan ko'tarilgan hodisada target boshqa bo'lishi mumkin (Hodisa tarqalishi).

Maslahat: Bo'sh boshlang'ich qiymat uchun defaultValue="" yozish shart emas — atributsiz maydon ham bo'sh. Bu yerda u "bu maydon ataylab uncontrolled" degan belgi bo'lib turibdi.

2.2 FormData — butun forma bir martada

Ko'p maydon, ichida checkbox'lar guruhi:

jsx
import { useState } from "react";

function App() {
  const [result, setResult] = useState("—");

  function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    const order = {
      ...Object.fromEntries(data), // { name, table, extras }
      extras: data.getAll("extras"), // bir nomli hammasi
    };
    setResult(JSON.stringify(order));
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Ism <input name="name" />
      </label>
      <label>
        Stol{" "}
        <select name="table" defaultValue="7">
          <option value="4">4</option>
          <option value="7">7</option>
        </select>
      </label>
      <label>
        <input type="checkbox" name="extras" value="non" />
        Non
      </label>
      <label>
        <input type="checkbox" name="extras" value="choy" />
        Choy
      </label>
      <button>Yuborish</button>
      <p>{result}</p>
    </form>
  );
}

Ekranda:

text
Ism Stol 47NonChoyYuborish
{"name":"Bobur","table":"7","extras":["non","choy"]}

Uch narsaga e'tibor bering:

  • Object.fromEntries(data) — FormData dagi juftliklardan oddiy obyekt yasaydi (Obyekt bo'ylab yurish). Lekin bir nomli bir nechta qiymatdan faqat oxirgisini oladi.
  • data.getAll("extras") — bir nomli hamma qiymatlar massivi. Checkbox guruhi uchun shu kerak.
  • Belgilanmagan checkbox FormData ga umuman kirmaydi. Shuning uchun getAll bo'sh massiv qaytaradi, get esa null.

FormData dagi hamma qiymat — satr (fayl maydonidan tashqari). Stol raqami "7", son emas. Oldingi darsdagi number input qoidasi bu yerda ham: hisoblashdan oldin Number().

Tekshirib ko'ring: Formada name atributisiz maydon bor. U FormData ga kiradimi?

Javob

Yo'q. FormData faqat name li, disabled bo'lmagan maydonlarni yig'adi. Uncontrolled formada name — maydonning "manzili": usiz qiymatni topib bo'lmaydi. Ko'p uchraydigan xato: id va <label htmlFor> bor, name esa unutilgan.

3. Brauzer validatsiyasi

3.1 required, pattern, min — tekin tekshiruv

Brauzer validatsiyasi darsidagi atributlar React'da ham ishlaydi. Maydon noto'g'ri bo'lsa, brauzer submit hodisasini umuman yubormaydi va maydon yonida o'z xabarini ko'rsatadi:

jsx
import { useState } from "react";

function App() {
  const [sent, setSent] = useState(false);
  return (
    <form
      onSubmit={(e) => {
        e.preventDefault();
        setSent(true);
      }}
    >
      <label>
        Telefon <input name="phone" required />
      </label>
      <button>Yuborish</button>
      <p>{sent ? "Yuborildi" : "Kutilmoqda"}</p>
    </form>
  );
}

Ekranda:

text
Telefon Yuborish
Kutilmoqda

Bo'sh required maydon — onSubmit chaqirilmadi. Chrome'da maydon yonida "Please fill out this field." (brauzer tiliga qarab o'zbekcha ham bo'lishi mumkin) qalqib chiqadi. Bu matnni brauzer yozadi, React emas.

3.2 O'z xabaringiz: noValidate va validity

Brauzer xabari dizaynga mos kelmasa yoki o'zbekcha bo'lishi kerak bo'lsa: formaga noValidate qo'yasiz (brauzer to'xtatmaydi, lekin atributlar tekshiruvi ishlayveradi) va natijani validity dan o'qiysiz:

jsx
import { useState } from "react";

function App() {
  const [error, setError] = useState(null);

  function handleSubmit(e) {
    e.preventDefault();
    const phone = e.currentTarget.elements.namedItem("phone");
    if (phone.validity.valueMissing) {
      setError("Telefon raqamini kiriting");
    } else if (phone.validity.patternMismatch) {
      setError("9 ta raqam, bo'sh joysiz: 901234567");
    } else {
      setError(null);
    }
  }

  return (
    <form onSubmit={handleSubmit} noValidate>
      <label>
        Telefon{" "}
        <input
          name="phone"
          required
          pattern="[0-9]{9}"
          inputMode="tel"
        />
      </label>
      <button>Yuborish</button>
      <p role="alert">{error ?? "Hammasi to'g'ri"}</p>
    </form>
  );
}

Ekranda:

text
Telefon Yuborish
9 ta raqam, bo'sh joysiz: 901234567

validity — har maydonda bor obyekt: valueMissing (bo'sh required), patternMismatch, rangeUnderflow (min dan kichik), typeMismatch (type="email" ga email emas) va boshqalar. elements.namedItem("phone") — formadagi name="phone" maydon. vazifalar dagi yangi vazifa formasi aynan noValidate bilan: xato matnini forma o'zi chiqaradi.

Diqqat: Brauzer tekshiruvi — faqat qulaylik. Hujumchi DevTools'da required ni o'chirib yoki so'rovni to'g'ridan-to'g'ri yuborib uni chetlab o'tadi. Haqiqiy tekshiruv serverda bo'lishi shart (Forma xavfsizligi).

4. React 19: form action

4.1 <form action={fn}>

React 19 da <form> ning action atributiga funksiya berish mumkin. React yuborishni o'zi ushlaydi (preventDefault shart emas) va funksiyaga tayyor FormData ni beradi:

jsx
import { useState } from "react";

function App() {
  const [dishes, setDishes] = useState(["Osh"]);

  function addDish(formData) {
    setDishes([...dishes, formData.get("dish")]);
  }

  return (
    <form action={addDish}>
      <label>
        Taom <input name="dish" />
      </label>
      <button>Qo'shish</button>
      <p>Menyu: {dishes.join(", ")}</p>
    </form>
  );
}

Ekranda:

text
Taom Qo'shish
Menyu: Osh, Manti

onSubmit bilan solishtiring: e.preventDefault() yo'q, new FormData(e.currentTarget) yo'q — React ikkalasini qildi. Yana bir farq ko'zga ko'rinmaydi: action tugagach, React formadagi uncontrolled maydonlarni tozaladi. "Manti" yozilgan maydon endi bo'sh — keyingi taomni yozishga tayyor. Ko'p hollarda aynan shu kerak.

Action funksiyasi async ham bo'lishi mumkin (serverga fetch). React uni transition ichida bajaradi — bu "shoshilinch emas" yangilanish. Tafsiloti — Actions darsida.

4.2 useActionState — natija va kutish holati

Haqiqiy forma serverga ketadi: kutish vaqti bor, javob muvaffaqiyat yoki xato. Bularni boshqarish uchun React 19 da useActionState hook'i:

jsx
const [state, formAction, isPending] =
  useActionState(action, initialState);
  • action(prevState, formData) — sizning funksiyangiz. U yangi state'ni qaytaradi (masalan, { error: "…" }).
  • initialState — birinchi renderdagi state.
  • state — action oxirgi marta qaytargan qiymat.
  • formAction — <form action={formAction}> ga beriladigan o'ralgan funksiya.
  • isPending — action bajarilayotganmi.

Bron so'rovi, server o'rniga 300 ms kutish bilan:

jsx
import { useActionState } from "react";

async function book(prevState, formData) {
  await new Promise((r) => setTimeout(r, 300)); // "server"
  const name = formData.get("name").trim();
  if (name === "") return { error: "Ism kerak", ok: null };
  return { error: null, ok: `${name}, stol bron qilindi!` };
}

function App() {
  const [state, formAction, isPending] = useActionState(book, {
    error: null,
    ok: null,
  });
  return (
    <form action={formAction}>
      <label>
        Ism <input name="name" />
      </label>
      <button disabled={isPending}>Bron qilish</button>
      {isPending && <p>Yuborilmoqda…</p>}
      {state.error && <p role="alert">{state.error}</p>}
      {state.ok && <p>{state.ok}</p>}
    </form>
  );
}

Ekranda:

text
Ism Bron qilish
Yuborilmoqda…

Tugma bosilgandan 60 ms keyin — action hali tugamagan: isPending true. Shu blok kut(400) bilan:

jsx
import { useActionState } from "react";

async function book(prevState, formData) {
  await new Promise((r) => setTimeout(r, 300)); // "server"
  const name = formData.get("name").trim();
  if (name === "") return { error: "Ism kerak", ok: null };
  return { error: null, ok: `${name}, stol bron qilindi!` };
}

function App() {
  const [state, formAction, isPending] = useActionState(book, {
    error: null,
    ok: null,
  });
  return (
    <form action={formAction}>
      <label>
        Ism <input name="name" />
      </label>
      <button disabled={isPending}>Bron qilish</button>
      {isPending && <p>Yuborilmoqda…</p>}
      {state.error && <p role="alert">{state.error}</p>}
      {state.ok && <p>{state.ok}</p>}
    </form>
  );
}

Ekranda:

text
Ism Bron qilish
Malika, stol bron qilindi!
Qadam isPending state Ekranda
boshida false { error: null, ok: null } forma
bosildi true o'sha Yuborilmoqda… (tugma bloklangan)
300 ms dan keyin false { ok: "Malika, …" } Malika, stol bron qilindi!

Bitta hook uchta state'ning o'rnini bosdi: natija, xato va "kutilmoqda". State strukturasi darsida qo'lda yozgan status ning tayyor shakli. Tugmaning disabled={isPending} — ikki marta bosishdan himoya. Ichki tugma komponentida isPending ni olish uchun useFormStatus bor (react-dom dan) — useActionState va useFormStatus darsida.

5. Tuzoq: action'dan keyin forma o'zi tozalanadi

5.1 Xato bo'lsa ham

Endi tekshiruv xato topgan holat. Mehmon ismni juda qisqa yozdi:

jsx
import { useActionState } from "react";

function check(prevState, formData) {
  const name = formData.get("name");
  if (name.length < 3) return { error: "Kamida 3 harf" };
  return { error: null };
}

function App() {
  const [state, formAction] = useActionState(check, { error: null });

  function showField() {
    const field = document.querySelector("[name=name]");
    console.log("Maydonda:", JSON.stringify(field.value));
  }

  return (
    <>
      <form action={formAction}>
        <label>
          Ism <input name="name" />
        </label>
        <button>Yuborish</button>
        <p>{state.error ?? "—"}</p>
      </form>
      <button onClick={showField}>Maydon?</button>
    </>
  );
}

Konsolda:

text
Maydonda: ""

Ekranda:

text
Ism Yuborish
Kamida 3 harf
Maydon?

Xato ko'rsatildi — lekin "Al" yo'qoldi. React action tugaganini ko'rdi va formani tozaladi. U natija "muvaffaqiyat"mi yoki "xato"mi — bilmaydi. Uzun izoh yozgan mehmon hammasini qaytadan yozishi kerak. Malika uchun bu yanada og'ir: ekran o'quvchi bilan maydonni qayta topish va yozish.

5.2 Yechim: qiymatni state orqali qaytarish

Action yozilgan qiymatni ham qaytarsin, maydon esa uni defaultValue sifatida olsin:

jsx
import { useActionState } from "react";

function check(prevState, formData) {
  const name = formData.get("name");
  if (name.length < 3) return { error: "Kamida 3 harf", name };
  return { error: null, name: "" };
}

function App() {
  const [state, formAction] = useActionState(check, {
    error: null,
    name: "",
  });

  function showField() {
    const field = document.querySelector("[name=name]");
    console.log("Maydonda:", JSON.stringify(field.value));
  }

  return (
    <>
      <form action={formAction}>
        <label>
          Ism <input name="name" defaultValue={state.name} />
        </label>
        <button>Yuborish</button>
        <p>{state.error ?? "—"}</p>
      </form>
      <button onClick={showField}>Maydon?</button>
    </>
  );
}

Konsolda:

text
Maydonda: "Al"

Ekranda:

text
Ism Yuborish
Kamida 3 harf
Maydon?

Tozalash baribir bo'ldi, lekin maydon "boshlang'ich qiymat" ga qaytadi — defaultValue esa endi state.name, ya'ni "Al". Bu ishlaydi, lekin har maydonni state'da qaytarish kerak — uncontrolled formaning soddaligi yo'qola boshlaydi.

5.3 form.reset() va defaultValue

Action'siz formada tozalashning eski yo'li — form.reset(). U maydonlarni boshlang'ich qiymatga qaytaradi. React defaultValue ni HTML value atributiga yozadi — brauzer uchun aynan u "boshlang'ich qiymat". Agar defaultValue bo'sh bo'lmasa:

jsx
function App() {
  function handleSubmit(e) {
    e.preventDefault();
    e.currentTarget.reset(); // "qo'shildi — tozalaymiz"
  }

  function showField() {
    const field = document.querySelector("[name=matn]");
    console.log("Maydonda:", JSON.stringify(field.value));
  }

  return (
    <>
      <form onSubmit={handleSubmit}>
        <label>
          Vazifa <input name="matn" defaultValue="Qoralama matni" />
        </label>
        <button>Qo'shish</button>
      </form>
      <button onClick={showField}>Maydon?</button>
    </>
  );
}

Konsolda:

text
Maydonda: "Qoralama matni"

reset() maydonni bo'shatmadi — defaultValue ga qaytardi. Bu HTML'ning to'g'ri xulqi, lekin "tozaladim" deb o'ylagan dasturchi uchun kutilmagan.

6. Nega vazifalar <form action> ni ishlatmaydi

6.1 O'lchov

vazifalar dagi yangi vazifa formasining ikki xususiyati bor:

  1. Maydonning defaultValue si — qoralama: sahifa yangilansa, yozilayotgan matn tiklanadi (16-qismda qo'shilgan; matn yozish to'xtagach saqlanadi).
  2. Tekshiruv xato topishi mumkin (bo'sh matn, takror vazifa, 200 belgidan uzun). Xato bo'lsa matn maydonda qolishi kerak — tuzatish uchun.

Kanonni yozishda ikkala yo'lni sinab ko'rdik. Birinchisi — <form action>, action esa "tekshiruv xato topdi" deb hech narsa qilmaydi. Haqiqiy Vitest natijasi (React 19.3). Chiqish uzun, lekin faqat ikki qatorga qarang: - bilan boshlangani — test kutgan qiymat, + bilan boshlangani — aslida olingan qiymat.

text
AssertionError: expected { actionKordi: [ 'non OLISH' ], …(1) } to deeply equal { actionKordi: [ 'non OLISH' ], …(1) }
- Expected
+ Received
  {
    "actionKordi": [
      "non OLISH",
    ],
-   "maydonda": "non OLISH",
+   "maydonda": "",
  }

"Kutilgan: maydonda non OLISH qoladi. Olingan: bo'sh satr." Action matnni ko'rdi, lekin forma tozalandi — «Xato bo'lsa ham» bo'limidagi tuzoq.

Ikkinchisi — onSubmit va muvaffaqiyatda form.reset(). Bu yerda ham - va + qatorlarini solishtiring:

text
AssertionError: expected [ 'Qoralama matni', 'Qoralama matni' ] to deeply equal [ 'Qoralama matni', '' ]
- Expected
+ Received
  [
    "Qoralama matni",
-   "",
+   "Qoralama matni",
  ]

Birinchi element — value atributi (React defaultValue ni yozgan), ikkinchisi — reset() dan keyingi qiymat. Vazifa qo'shildi, lekin maydonda eski qoralama qaytib keldi.

6.2 Kanondagi yechim

YangiVazifaForma.tsx (kanondan, qisqartirilgan):

tsx
  // Uncontrolled + FormData (17/#19): qiymat har harfda emas, yuborilganda
  // kerak — formadan name="matn" bo'yicha o'qiladi. <form action={fn}>
  // emas: React action'dan keyin formani O'ZI tozalaydi — tekshiruv
  // xato topsa ham, va matn yo'qoladi (o'lchandi, 19-form-action.txt)
  function yuborildi(event: FormEvent<HTMLFormElement>) {
    event.preventDefault(); // sahifa qayta yuklanmasin
    const maydon = maydonniOl(event.currentTarget);
    const matn = new FormData(event.currentTarget).get("matn");
    if (maydon === null || typeof matn !== "string") {
      return;
    }
    const natija = onQosh(matn.trim());
    switch (natija.tur) {
      case "xato":
        setXato(natija.xabar);
        maydon.focus();
        return;
      case "saqlanmadi":
        return; // e'lonni asosiy.ts qildi, matn maydonda qoladi
      case "qoshildi":
        setXato(null);
        setTakliflar([]);
        // form.reset() EMAS: React defaultValue ni value atributiga
        // yozadi va reset() qoralamani qaytarardi (o'lchandi)
        maydon.value = "";
        return;
  • onSubmit + preventDefault — forma faqat biz xohlaganda tozalanadi.
  • FormData(...).get("matn") — turi FormDataEntryValue | null (satr yoki File), shuning uchun typeof matn !== "string" tekshiruvi (Narrowing).
  • Xato bo'lsa — matn joyida, fokus maydonga.
  • Muvaffaqiyatda — maydon.value = "": aynan bitta maydonni bo'shatish, reset() emas.

Form action — yaxshi vosita, lekin u "har yuborishdan keyin forma yangidan boshlanadi" degan farazga qurilgan. Bizning formada bu faraz to'g'ri emas. Action'lar server bilan ishlaganda (Next.js Server Actions, 23-qism) ayniqsa qulay. U yerda xato holatida qiymatni qaytarish — «Yechim: qiymatni state orqali qaytarish» bo'limidagi usul. React'da formani qo'lda tozalatish uchun react-dom dagi requestFormReset(form) ham bor — Actions darsida.

flowchart TD
  S["Forma yuborildi"] --> Q{"Qiymat har harfda kerakmi?"}
  Q -- "ha: tekshiruv, format" --> C["Controlled: value + onChange"]
  Q -- "yo'q" --> X{"Xatoda matn qolishi kerakmi?"}
  X -- "yo'q yoki serverda" --> A["form action + useActionState"]
  X -- "ha, oddiy forma" --> F["onSubmit + FormData"]

7. Vazifalar qadami: FormData, eksport va import React'da

7.1 Yangi vazifa — FormData

Oldingi darsda ochilgan refactor/formalar branch'ida davom etamiz. YangiVazifaForma dagi maydon uncontrolled qoladi, lekin qiymat endi FormData dan:

diff
-  const matn = kiritma.value.trim();                 // tahrirMaydoni() — DOM'dan
+  const [matn, setMatn] = useState(vazifa.matn);    // TahrirQatori state'i
+  <input value={matn} onChange={(event) => setMatn(event.target.value)} … />
+  const matn = new FormData(event.currentTarget).get("matn");   // yangi vazifa, import

Bu — bitta fayl diff'i emas, uch fayldagi asosiy qatorlarning qisqa xulosasi. Unda ikki formalar darsi birga: controlled tahrir (Formalar I) va FormData (bugun).

7.2 Import — uncontrolled textarea

Eksport va import hali index.html da vanilla edi. Endi ular komponent. Import.tsx dagi forma (kanondan):

tsx
  // FormData (17/#19): formadagi name="paket" maydon qiymati
  function matndan(event: FormEvent<HTMLFormElement>) {
    event.preventDefault();
    const matn = new FormData(event.currentTarget).get("paket");
    importQil(typeof matn === "string" ? matn : "", "matn");
  }
…
      <form onSubmit={matndan}>
        <label htmlFor="import-matn">Eksport qilingan JSON matni</label>
        <textarea
          id="import-matn"
          name="paket"
          rows={6}
          aria-invalid={xato?.manba === "matn" ? true : undefined}
        />

Import matni bir necha kilobayt bo'lishi mumkin — har harfda state yangilash ma'nosiz. Fayl maydoni (<input type="file">) esa React'da har doim uncontrolled: xavfsizlik sababli uning qiymatini JavaScript yoza olmaydi (faqat bo'shatish mumkin). Kanonda shunday:

tsx
    // O'sha faylni qayta tanlash ham change hodisasini bersin
    event.currentTarget.value = "";

Sudrab tashlashda fayl zona ustidami — ustida state'i, className esa undan hisoblanadi. Brauzer tekshiruvida (e2e ssenariy — sahifani foydalanuvchi kabi bosib chiqadigan skript) bitta o'zgarish shu yerdan chiqdi. Endi class="ustida" React state'idan keladi va hodisa tugagach chiziladi — ssenariy uni o'qishdan oldin 50 ms kutadi. 159 tekshiruvning soni va matni o'zgarmadi.

7.3 Eksport — controlled, lekin faqat o'qish uchun

tsx
      <textarea id="eksport-matn" rows={6} readOnly value={json} />

value + readOnly — Formalar I dagi ogohlantirishning uchinchi yo'li: maydon faqat ko'rsatadi. JSON matni useState da, tugma bosilganda yangilanadi.

7.4 Tekshiruv va commit

bash
npm run build && npm test && npm run lint && npm run tip

Kanon natijalari: node:test — 149, Vitest — 22 (yangi EksportImport.test.tsx), brauzer — 159/159. Diff (holat 16 → 19, ikki dars birga): 12 fayl, +337 −165. index.html dan eksport va import bloklari olib tashlandi — App ga <Eksport /> va <Import /> qo'shildi.

text
refactor: controlled tahrir, FormData, eksport/import React'da

Body: "TahrirQatori — useState(vazifa.matn) (fon yangilanganda matn state'da qoladi; fondaChiz qoralama tiklashi olib tashlandi). Yangi vazifa va import — FormData. Eksport, Import komponentlari (drag — ustida state)." PR'ni birlashtiring.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
maydon FormData da yo'q name atributi yo'q name qo'shing
sahifa qayta yuklanadi onSubmit da preventDefault yo'q e.preventDefault() yoki action={fn}
checkbox'lardan bittasi keldi Object.fromEntries oxirgisini oladi getAll("nom")
"7" + 1 = "71" FormData qiymati — satr Number()
action'dan keyin xato bo'lsa ham matn yo'qoldi React formani o'zi tozalaydi qiymatni state'da qaytaring yoki onSubmit
reset() dan keyin eski matn reset() defaultValue ga qaytaradi kerakli maydonni value = ""
defaultValue o'zgardi, maydon o'zgarmadi u faqat boshlang'ich qiymat key bilan qayta yarating (State'ning saqlanishi)

9. Mashqlar

Mashqlar kurs/mashqlar/17/19-uncontrolled-formalar/ da.

1-mashq (oson): FormData natijasi

Formada: <input name="name"> ("Ali"), ikkita <input type="checkbox" name="extra"> (value="non" belgilangan, value="choy" belgilanmagan), <input name="note" disabled> ("izoh").

  • data.get("name"):
  • data.getAll("extra") dagi elementlar soni:
  • data.get("note"):
Yechim

get("name") — "Ali". Belgilanmagan checkbox kirmaydi — getAll("extra") → ["non"], bitta. disabled maydon ham FormData ga kirmaydi — get("note") → null.

2-mashq (o'rta): "Fikr bildiring" formasi

Uncontrolled forma: ism (required), baho (select: 1–5, sukut bo'yicha 5), izoh (textarea). onSubmit + FormData. Yuborilgach pastda: "Ali: — Juda mazali" (baho nechta bo'lsa, shuncha yulduz; izoh bo'sh bo'lsa — faqat "Ali: "). Baho son bo'lsin (Number), ★ — baho soniga teng marta takrorlansin ("★".repeat(n), satr metodlari). Hech qanday maydon state'ga bog'lanmasin — state faqat natija uchun.

Yechim
jsx
import { useState } from "react";

function App() {
  const [review, setReview] = useState(null);

  function handleSubmit(e) {
    e.preventDefault();
    const data = new FormData(e.currentTarget);
    const stars = Number(data.get("stars"));
    const note = data.get("note").trim();
    const text = `${data.get("name")}: ${"★".repeat(stars)}`;
    setReview(note === "" ? text : `${text} — ${note}`);
  }

  return (
    <form onSubmit={handleSubmit}>
      <label>
        Ism <input name="name" required />
      </label>
      <select name="stars" defaultValue="5" aria-label="Baho">
        {[1, 2, 3, 4, 5].map((n) => (
          <option key={n} value={n}>
            {n}
          </option>
        ))}
      </select>
      <label>
        Izoh <textarea name="note" />
      </label>
      <button>Yuborish</button>
      {review && <p>{review}</p>}
    </form>
  );
}

Ekranda:

text
Ism 12345Izoh Yuborish
Ali: ★★★★★ — Zo'r

Maydonlar uncontrolled — yozish paytida render yo'q. select ning defaultValue="5", <option selected> emas. Bu safar textarea ga yozilgan matn ekran natijasida yo'q: React unga tegmagan. Oldingi darsdagi controlled textarea da esa React har o'zgarishda matnni element ichiga ham yozadi — shuning uchun u ko'ringan edi.

3-mashq (qiyin): Bron — useActionState bilan, matn yo'qolmasin

useActionState bilan bron formasi: ism va mehmonlar soni. Action (async, 200 ms "server"): ism bo'sh bo'lsa — "Ism kerak"; soni 1–12 oralig'ida bo'lmasa — "1 dan 12 gacha". Xato bo'lsa, ikkala maydondagi qiymat yo'qolmasin («Yechim: qiymatni state orqali qaytarish» bo'limidagi usul). Muvaffaqiyatda: "Malika, 4 kishi — bron qilindi" va forma bo'sh. Kutish paytida tugma bloklangan.

Yechim
jsx
import { useActionState } from "react";

const EMPTY = { error: null, done: null, name: "", guests: "" };

async function book(prev, formData) {
  await new Promise((r) => setTimeout(r, 200));
  const name = formData.get("name").trim();
  const guests = formData.get("guests");
  const n = Number(guests);
  const values = { done: null, name, guests };
  if (name === "") return { ...values, error: "Ism kerak" };
  if (!Number.isInteger(n) || n < 1 || n > 12) {
    return { ...values, error: "1 dan 12 gacha" };
  }
  return { ...EMPTY, done: `${name}, ${n} kishi — bron qilindi` };
}

function App() {
  const [state, formAction, isPending] = useActionState(book, EMPTY);
  return (
    <form action={formAction}>
      <label>
        Ism <input name="name" defaultValue={state.name} />
      </label>
      <label>
        Soni{" "}
        <input
          type="number"
          name="guests"
          defaultValue={state.guests}
        />
      </label>
      <button disabled={isPending}>Bron</button>
      <p role="status">
        {isPending
          ? "Yuborilmoqda…"
          : (state.error ?? state.done ?? "")}
      </p>
    </form>
  );
}

Ekranda:

text
Ism Soni Bron
1 dan 12 gacha

Xato bo'ldi, lekin "Malika" va "0" maydonlarda qoladi: React formani tozaladi, maydonlar esa yangi defaultValue (state.name, state.guests) ga qaytdi. "0" ni "4" ga tuzatib, qayta yuborsangiz — "Malika, 4 kishi — bron qilindi" va maydonlar bo'sh (EMPTY). Tekshiruv uchun alohida test: shu harakat oxirida maydon qiymatini konsolga chiqarib ko'ring.

4-mashq: Vazifalar qadami

refactor/formalar branch'ida «Vazifalar qadami» dagi o'zgarishlarni kiriting: YangiVazifaForma.tsx (FormData), Import.tsx, Eksport.tsx (yangi), App.tsx ga ikkalasi, index.html dan eksport/import bloklari, asosiy.ts dagi importQil(matn): string | null. Keyin tekshiruvlar, commit va PR.

Yechim
  • npm run tip: get("matn") ning turi FormDataEntryValue | null — typeof … !== "string" tekshiruvisiz .trim() TS2339 beradi (File da trim yo'q).
  • Brauzerda: bo'sh vazifa qo'shing — xato chiqadi, maydonga yozilgan bo'shliqlar ham joyida. To'g'ri vazifa — maydon bo'sh, sahifani yangilang — qoralama ham bo'sh.
  • Import'ga buzuq JSON tashlang — xato matni chiqadi, textarea'dagi matn qoladi (uncontrolled, React unga tegmaydi).
  • npm test — node 149, Vitest 22.

Yakuniy kod — refactor: controlled tahrir, FormData, eksport/import React'da commit'i.

10. Real ishda

  • Ko'p formalar uncontrolled — login, ro'yxatdan o'tish, "biz bilan bog'laning". React Hook Form (RHF I) aynan uncontrolled maydonlar va name ustiga qurilgan — shuning uchun tez.
  • Form action'lar — Next.js va React Server Components dunyosida asosiy yo'l (23-qism): action serverdagi funksiya bo'lishi mumkin. Xatoda qiymatni qaytarish — o'sha yerda ham kerak bo'ladigan usul.
  • Progressive enhancement: oddiy HTML forma JavaScript yuklanmasdan ham ishlaydi. Server actions bilan React formasi ham shunday ishlay oladi — uncontrolled + name bunga asos.
  • Intervyu savollari: "Uncontrolled input qachon afzal?", "FormData dan obyekt qanday olinadi va nimasi yo'qoladi?", "React 19 form action nima qiladi?", "useActionState nimani qaytaradi?".

Xulosa

  • Uncontrolled maydon: qiymat DOM'da, React faqat defaultValue/defaultChecked beradi; name — manzil.
  • new FormData(form) — butun forma; Object.fromEntries — obyekt, getAll — bir nomlilar; hammasi satr.
  • Brauzer validatsiyasi ishlaydi; o'z xabaringiz uchun noValidate + validity. Server tekshiruvi baribir shart.
  • <form action={fn}> — FormData ni o'zi beradi; useActionState — [state, formAction, isPending].
  • Action'dan keyin React formani tozalaydi, xatoda ham; reset() esa defaultValue ga qaytaradi — ikkalasi o'lchangan.
  • vazifalar: yangi vazifa va import — onSubmit + FormData, tahrir — controlled, eksport — readOnly.

Keyingi dars: React DevTools bilan ishlash — komponent daraxti, props, state va hooklarni brauzerda ko'rish va o'zgartirish.

Manbalar

  • React: <form> (action) — react.dev/reference/react-dom/components/form
  • React: useActionState — react.dev/reference/react/useActionState
  • React blog: "React v19" (2024-12-05) — Actions, useActionState — react.dev/blog
  • MDN: FormData, ValidityState — developer.mozilla.org
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Formalar II: uncontrolled inputlar, FormData va React 19 form action — IlmHamroh