IlmHamroh
JavaScript Full-stack/18-qism. React chuqur5/42-dars22 daqiqa
Mundarija (33)

Actions: async transitionlar va form action — React 19 da kutish, xato va forma reset

Qisqacha: React 19 da transition ichida chaqirilgan funksiya Action deyiladi. U oddiy ham, async ham bo'lishi mumkin. React Action'ni kuzatadi: await qilingan so'rov qaytguncha isPending — true. Action ichidagi hamma yangilanish esa u tugagach birga chiziladi. <form action={fn}> va tugmadagi formAction={fn} — Action'ni ishga tushirishning tayyor yo'li: React FormData ni o'zi beradi, transition'ni o'zi ochadi va muvaffaqiyatdan keyin uncontrolled maydonlarni o'zi tozalaydi. Formani qo'lda tozalash kerak bo'lsa — requestFormReset(form), faqat Action ichida. Action ichida tashlangan xato eng yaqin error boundary'ga ketadi.

Bu darsda

  • Action nima ekanini va reducer'dagi "amal (action)" dan farqini tushuntira olasiz.
  • startTransition(async () => …) da isPending va yangilanishlar qachon chizilishini jonli ko'rasiz — jumladan "Saqlanmoqda…" nega ko'rinmay qolishini.
  • <form action> va formAction bilan bir formada ikki xil yuborish yozasiz.
  • Avtomatik reset qaysi maydonlarga tegishini va requestFormReset qachon kerakligini bilasiz.
  • Action ichidagi xato qayerga ketishini ko'rasiz va vazifalar dagi server yozuvini Action'ga aylantirasiz (1-qism).

Oldin bilishingiz kerak: useTransition, Formalar II: form action, async / await, useReducer.

1. Nega bu kerak?

Oldingi darsda ma'lumotni o'qishni ko'rdik: Promise, use, Suspense. Endi teskari yo'nalish — ma'lumotni serverga yozish. Jasur aka «Bahor» saytida stol bron qilish formasini xohlaydi.

Sardor uni o'zi bilgan usulda yozdi. Unda isSaving state'i, try/finally va xato uchun yana bir state bor. Yana ikki marta bosishdan himoya va oxirida maydonlarni qo'lda bo'shatish. 40 qatorning 25 tasi bron haqida emas, "kutish" va "tozalash" haqida edi. Bunday kod har formada takrorlanadi va har safar bir joyi unutiladi. Bir formada finally yo'q edi: server xato qaytardi va tugma abadiy bloklangan qoldi.

Hayotiy o'xshatish: pochta bo'limida jo'natma topshirish. Siz qutini beraverasiz. Xodim o'zi tarozida tortadi, o'zi chek yozadi, o'zi qutini javonga qo'yadi va "keyingisi!" deydi. Siz "tortilyaptimi, chek yozildimi" deb har qadamni kuzatmaysiz. Action — React'ga beriladigan shunday quti: siz faqat "nima yuboriladi" ni yozasiz, kutish, tartib va tozalashni React o'zi bajaradi.

2. Action nima

2.1 Ta'rif

Action — transition ichida chaqiriladigan funksiya. useTransition darsida startTransition ga async funksiya bergan edik va React hujjati uni shunday atashini aytgan edik. Bugun shu g'oyani oxirigacha ko'ramiz.

Action'ni React ishga tushiradigan uch yo'l bor:

Qayerda Kim chaqiradi
startTransition(fn) yoki useTransition siz, hodisa ishlovchisida
<form action={fn}>, <button formAction={fn}> React, forma yuborilganda
useActionState qaytargan funksiya React yoki siz (keyingi dars)

Diqqat: useReducer darsida dispatch({ tur: "qoshildi" }) dagi obyektni amal (action) deb atagan edik. Bu boshqa narsa: u — "nima bo'ldi" degan oddiy obyekt. Bugungi Action esa funksiya, odatda async. Nomlari bir xil, chunki ikkalasi ham "foydalanuvchi qilgan ish" ni bildiradi. Darsda funksiyani doim katta harf bilan — Action — yozamiz.

2.2 Qo'lda va Action bilan

Avval eski usulni ko'ramiz. Bu — Sardorning formasi, qisqartirilgan:

jsx
const [isSaving, setIsSaving] = useState(false);
const [error, setError] = useState(null);

async function handleClick() {
  setIsSaving(true);
  setError(null);
  try {
    await saveBooking();
  } catch (err) {
    setError(err.message);
  } finally {
    setIsSaving(false); // ❌ unutilsa — tugma abadiy bloklangan
  }
}

Action bilan isSaving va finally yo'qoladi — kutish holatini React o'zi biladi:

jsx
const [isPending, startTransition] = useTransition();

function handleClick() {
  startTransition(async () => {
    await saveBooking(); // isPending shu qaytguncha true
  });
}

Qolgan farqlar jadvalda:

Savol Qo'lda Action
"kutilmoqda" holati useState + finally React o'zi (isPending)
bir necha bosish har biri o'z setIsSaving i isPending hammasi tugaguncha true
oraliq kadrlar har setter alohida chiziladi hammasi Action tugagach, birga
xato catch + state error boundary yoki qiymat (useActionState)
forma tozalash qo'lda <form action> da avtomatik

Jadvaldagi uchinchi qator — eng kutilmagani. Uni alohida ko'ramiz.

3. Action ichidagi yangilanishlar

3.1 "Saqlanmoqda…" ko'rinmadi

Tabiiy fikr: "So'rovdan oldin xabarni o'rnataman, keyin await":

jsx
import { useState, useTransition } from "react";

function saveBooking() {
  return new Promise((resolve) => setTimeout(resolve, 300));
}

export default function App() {
  const [message, setMessage] = useState("Bron yo'q");
  const [isPending, startTransition] = useTransition();
  console.log("render:", isPending, message);

  function handleClick() {
    startTransition(async () => {
      setMessage("Saqlanmoqda…"); // ❌ ko'rinmaydi
      await saveBooking();
      startTransition(() => setMessage("5-stol bron qilindi"));
    });
  }

  return (
    <div>
      <button onClick={handleClick}>Saqlash</button>
      <p>{message}</p>
    </div>
  );
}

Konsolda:

text
render: false Bron yo'q
render: true Bron yo'q
render: true Bron yo'q
render: false 5-stol bron qilindi

Ekranda:

text
Saqlash
5-stol bron qilindi

Harakat qatoridagi ekran("Bron yo'q") — bosilgandan 60 ms keyin, ya'ni so'rov hali yo'lda. Ekranda hali eski matn turibdi. Konsolda ham "Saqlanmoqda…" bir marta ham yo'q.

3.2 Nega

setMessage("Saqlanmoqda…") transition ichida chaqirildi — u ham Action'ning bir qismi. React Action'ga tegishli yangilanishlarni ushlab turadi va Action tugaganda hammasini bitta renderda chizadi. Oxirgi qiymat "5-stol bron qilindi" — oraliq qiymat hech qachon ekranga chiqmaydi.

sequenceDiagram
  participant U as Foydalanuvchi
  participant R as React
  participant S as Server
  U->>R: bosish
  R->>R: isPending: true (shoshilinch render)
  R->>S: Action: setMessage + await
  Note over R: Action yangilanishlari kutadi
  S-->>R: javob (300 ms)
  R->>R: hammasi birga: isPending false + yangi matn

Diagrammada asosiy ikki nuqta bor: bosilganda (isPending: true) va Action tugaganda. Orada React yangi matnni chizmaydi. Bu ataylab qilingan: foydalanuvchi "Saqlanmoqda… → Bron yo'q → 5-stol" kabi sakrashni ko'rmaydi.

Unda "kutilmoqda" ni qanday ko'rsatamiz? Uchta yo'l:

  • isPending — React o'zi beradi ({isPending ? "Saqlanmoqda…" : message}).
  • useFormStatus — forma ichidagi tugma uchun (keyingi dars).
  • useOptimistic — Action davomida darhol ko'rinadigan qiymat (useOptimistic).

vazifalar ning kanonida aynan shu tuzoq topildi. Forma Action'i ichida «qo'shildi» e'lonini oddiy setter bilan qo'yish e'lonni server javobigacha yashirardi. Shuning uchun u useOptimistic ga o'tdi — buni useOptimistic darsida ko'ramiz.

3.3 await dan keyin — yana startTransition

Kodda await dan keyingi setter startTransition(() => …) ichida. Sababi useTransition darsidagi "ma'lum cheklov": await dan keyin React "transition ichidamiz" ekanini unutadi. O'ralmasa, setter shoshilinch bo'lib, Action tugashini kutmasdan chiziladi. Qoida o'zgarmadi: await dan keyingi har setter — yana startTransition ichida.

Tekshirib ko'ring: Konsolda nega aynan to'rt qator? Qaysi render isPending ni true qildi?

Javob

1-qator — birinchi chizish. 2-qator — bosish paytidagi shoshilinch render: React faqat isPending: true ni chizdi. 3-qator — transition renderi: React Action'ni boshladi, lekin u hali await da. Shu render ham message ni eski holda ko'radi — "Saqlanmoqda…" Action'ga bog'langan va kutib turibdi. 4-qator — Action tugadi: isPending: false va oxirgi message bitta renderda. "Saqlanmoqda…" uchun render bo'lmadi — uning ustidan yozildi.

3.4 Bir nechta Action

Foydalanuvchi tugmani tez-tez ikki marta bossa-chi? Har bosish — alohida Action. React ularni birlashtiradi: isPending ikkalasi ham tugaguncha true turadi.

jsx
import { useState, useTransition } from "react";

let saved = 0; // "server"dagi buyurtmalar

function addToServer() {
  return new Promise((resolve) => {
    setTimeout(() => resolve(++saved), 500);
  });
}

export default function App() {
  const [count, setCount] = useState(0);
  const [isPending, startTransition] = useTransition();

  function handleClick() {
    startTransition(async () => {
      const total = await addToServer();
      console.log("server javobi:", total);
      startTransition(() => setCount(total));
    });
  }

  return (
    <div>
      <button onClick={handleClick}>+1 osh</button>
      <p>
        {isPending ? "Saqlanmoqda…" : `Buyurtmada: ${count} osh`}
      </p>
    </div>
  );
}

Konsolda:

text
server javobi: 1
server javobi: 2

Ekranda:

text
+1 osh
Buyurtmada: 2 osh

Vaqt bo'yicha (taxminan): 0 ms — 1-bosish, 360 ms — 2-bosish, 500 ms — 1-javob, 860 ms — 2-javob. Harakat qatoridagi ikkinchi ekran ~680 ms da — 1-javob kelib bo'lgan. Lekin ekranda hali "Saqlanmoqda…" — "1 osh" bir zumga ham chiqmadi. React ikkinchi Action tugashini kutdi va oxirgi holatni bir marta chizdi.

Bu odatda yaxshi: ekran sakramaydi. Lekin Action'lar parallel ishlayotganini unutmang. Server javoblari teskari tartibda kelsa, eski javob yangisining ustiga yozilishi mumkin. Navbatni React o'zi qiladigan vosita — useActionState: u Action'larni ketma-ket bajaradi.

4. <form action> — Action'ning tabiiy uyi

4.1 Qisqa takror

Formalar II darsida ko'rgansiz: <form action={fn}> ga funksiya bersangiz, React yuborishni ushlaydi, preventDefault qiladi va funksiyaga FormData beradi. Bugun bitta muhim gapni qo'shamiz: React bu funksiyani transition ichida chaqiradi. Ya'ni forma Action'i «Action ichidagi yangilanishlar» bo'limidagi qoidalarning hammasiga bo'ysunadi:

  • ichidagi setterlar Action tugagach birga chiziladi;
  • await dan keyingi setter — yana startTransition ichida;
  • xato — error boundary'ga.

Form action uchun yana uchta imkoniyat bor: tugmadagi formAction, avtomatik reset va requestFormReset.

4.2 formAction: bitta forma — ikki Action

Jasur akaning bron formasida ikki tugma bor: «Bron qilish» va «Qoralamaga». Ma'lumot bir xil, ishi boshqa. Tugmaning formAction prop'i formaning action ini shu tugma uchun almashtiradi:

jsx
import { useState } from "react";

export default function App() {
  const [log, setLog] = useState([]);

  function book(formData) {
    setLog((l) => [...l, `bron: ${formData.get("name")}`]);
  }

  function saveDraft(formData) {
    setLog((l) => [...l, `qoralama: ${formData.get("name")}`]);
  }

  return (
    <form action={book}>
      <label>
        Ism <input name="name" />
      </label>
      <button>Bron qilish</button>{" "}
      <button formAction={saveDraft}>Qoralamaga</button>
      <ul>
        {log.map((line, i) => (
          <li key={i}>{line}</li>
        ))}
      </ul>
    </form>
  );
}

Ekranda:

text
Ism Bron qilish Qoralamaga
qoralama: Malika
bron: Bobur

Har tugma o'z Action'ini chaqirdi, FormData esa ikkalasida bir xil forma maydonlaridan olindi. HTML'da formaction atributi bor edi — u boshqa manzil (URL) berardi. React'da unga funksiya beriladi. Ikkinchi narsaga e'tibor bering: Bobur yozilganda maydonda "Malika" yo'q edi — birinchi Action'dan keyin forma tozalangan.

4.3 Avtomatik reset: kimga tegadi

Action muvaffaqiyatli tugagach React formani reset qiladi — brauzerning form.reset() i kabi. Lekin faqat uncontrolled maydonlarni. Controlled maydon (value + onChange) qiymati state'da — React uni o'zi chizadi, reset unga tegmaydi.

jsx
import { useState } from "react";

export default function App() {
  const [name, setName] = useState("");

  function send(formData) {
    const name = formData.get("name");
    console.log("yuborildi:", name, "|", formData.get("note"));
  }

  function showFields() {
    const name = document.querySelector("[name=name]").value;
    const note = document.querySelector("[name=note]").value;
    console.log("ism:", name, "| izoh:", note);
  }

  return (
    <>
      <form action={send}>
        <label>
          Ism{" "}
          <input
            name="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
          />
        </label>
        <label>
          Izoh <input name="note" />
        </label>
        <button>Yuborish</button>
      </form>
      <button onClick={showFields}>Maydonlar?</button>
    </>
  );
}

Konsolda:

text
yuborildi: Ali | Deraza oldi
ism: Ali | izoh:

name — controlled, "Ali" joyida qoldi. note — uncontrolled, bo'shab qoldi. Controlled maydonni tozalash kerak bo'lsa — Action ichida setName("").

Reset faqat Action muvaffaqiyatli tugaganda bo'ladi. Action xato tashlasa, forma o'zgarmaydi — buni «Action ichidagi xato» bo'limida ko'ramiz. Action qiymat qaytarsa ham (masalan, "Ism kerak"), React uni muvaffaqiyat deb biladi va tozalaydi. Bu — Formalar II dagi "xato bo'lsa ham matn yo'qoladi" tuzog'i. Yechimi o'sha darsda edi: yozilgan qiymatni useActionState bilan qaytarib, defaultValue ga berish. Keyingi darsda vazifalar aynan shuni qiladi.

4.4 requestFormReset: o'zingiz tozalang

Ba'zan forma onSubmit bilan yoki tugmadagi onClick bilan ishlaydi. Shunda ham React'dagi reset kerak bo'lishi mumkin. Buning uchun react-dom da requestFormReset(form) bor: "shu Action tugagach formani tozala". U faqat Action ichida ishlaydi:

jsx
import { useRef } from "react";
import { requestFormReset } from "react-dom";

export default function App() {
  const formRef = useRef(null);

  function showField() {
    const field = formRef.current.querySelector("[name=dish]");
    console.log("maydonda:", JSON.stringify(field.value));
  }

  return (
    <>
      <form ref={formRef}>
        <label>
          Taom <input name="dish" />
        </label>
        <button
          type="button"
          onClick={() => requestFormReset(formRef.current)} // ❌
        >
          Tozalash
        </button>
      </form>
      <button onClick={showField}>Maydon?</button>
    </>
  );
}

Konsolda:

text
requestFormReset was called outside a transition or action. To fix, move to an action, or wrap with startTransition.
maydonda: ""

"requestFormReset transition yoki Action'dan tashqarida chaqirildi. Tuzatish uchun uni Action'ga ko'chiring yoki startTransition ga o'rang."

Maydon bu safar baribir tozalandi: React dev'da ogohlantirdi va reset'ni oddiy, shoshilinch yangilanish sifatida bajardi. Ogohlantirish — "kod noto'g'ri" belgisi. Production'da u chiqmaydi, reset esa Action natijasidan alohida chiziladi. Xabarning o'zi davoni aytyapti:

jsx
import { startTransition, useRef } from "react";
import { requestFormReset } from "react-dom";

export default function App() {
  const formRef = useRef(null);

  function showField() {
    const field = formRef.current.querySelector("[name=dish]");
    console.log("maydonda:", JSON.stringify(field.value));
  }

  function handleReset() {
    startTransition(() => {
      requestFormReset(formRef.current); // ✅ Action ichida
    });
  }

  return (
    <>
      <form ref={formRef}>
        <label>
          Taom <input name="dish" />
        </label>
        <button type="button" onClick={handleReset}>
          Tozalash
        </button>
      </form>
      <button onClick={showField}>Maydon?</button>
    </>
  );
}

Konsolda:

text
maydonda: ""

Nega bunday cheklov? Reset — Action natijasi bilan birga chizilishi kerak. Action davom etayotganda tozalansa, foydalanuvchi bo'sh maydonni va eski ro'yxatni bir vaqtda ko'radi. React reset'ni Action'ning boshqa yangilanishlari bilan bitta commit'ga qo'yadi.

form.reset() dan farqi ham shu: form.reset() DOM'ni darhol tozalaydi, React esa bundan bexabar qoladi. requestFormReset — React'ning navbati orqali.

Tekshirib ko'ring: Formada uchta maydon bor: ism (controlled), telefon (uncontrolled, defaultValue=""), izoh (uncontrolled, defaultValue="Deraza oldi"). Action muvaffaqiyatli tugadi. Maydonlarda nima qoladi?

Javob

Ism — yozilgan qiymat (controlled, state'dan). Telefon — bo'sh. Izoh — "Deraza oldi": reset maydonni bo'sh emas, boshlang'ich qiymatga qaytaradi. React defaultValue ni HTML value atributiga yozadi, brauzer esa reset'da aynan shu atributni oladi (Formalar II, form.reset() bo'limi).

5. Action ichidagi xato

5.1 Tashlangan xato — chegaragacha

Server 500 qaytardi va Action throw qildi. Kim ushlaydi?

jsx
async function book(formData) {
  await new Promise((resolve) => setTimeout(resolve, 200));
  throw new Error("500: server xatosi");
}

export default function App() {
  return (
    <form action={book}>
      <label>
        Ism <input name="name" />
      </label>
      <button>Bron qilish</button>
    </form>
  );
}

Konsolda:

text
Uncaught Error: 500: server xatosi

Natija oynasida — "Komponentni chizishda xato" qutisi, forma yo'q. React Action'dagi xatoni render xatosi kabi ko'rdi: eng yaqin error boundary'ni qidirdi, topmadi va butun ilovani o'chirdi (React ilovani debug qilish dagi oq ekran). useTransition bergan startTransition dagi xato ham xuddi shunday.

Bitta istisno bor: hook'siz startTransition (import { startTransition } from "react"). U hech qaysi komponentga bog'lanmagan — React xatoni qaysi chegaraga berishni bilmaydi. Xato Uncaught Error bo'lib konsolga tushadi, ilova esa joyida qoladi. Foydalanuvchi esa hech narsa ko'rmaydi — bu yanada yomon. Shuning uchun hook'siz startTransition dagi Action xatoni o'zi ushlashi kerak. vazifalar dagi yozuvniYubor aynan shunday: u hech qachon reject bo'lmaydi.

Bu ataylab. Oddiy async funksiyadagi xato ko'pincha jimgina yo'qoladi — konsolda bitta qizil qator, ekranda hech narsa. Action'da React uni ko'rinadigan qiladi. Kutilmagan xato (server yiqildi) uchun to'g'ri joy — error boundary: Error boundary darsida uni yozamiz va Action xatosini ushlaymiz.

5.2 Kutilgan xato — qiymat

"Ism kerak", "bu stol band" — bular kutilgan javob, ilovani yiqitmasligi kerak. Ularni throw emas, qaytarish kerak. Oddiy Action natija qaytara olmaydi — <form action> qaytgan qiymatni e'tiborsiz qoldiradi. Shuning uchun:

  • kichik holatda — try/catch va state (lekin catch dagi setter ham Action tugagach chiziladi);
  • formada — useActionState: Action'ning qaytgan qiymati — yangi state (keyingi dars).

vazifalar dagi yozuvniYubor ham shunday: u hech qachon reject bo'lmaydi va { ok: false, xabar } qaytaradi. Buni «Vazifalar qadami» bo'limida ko'ramiz.

Xato turi Misol Qanday
kutilgan "Ism kerak", 409 band qiymat qaytarish (useActionState)
kutilmagan 500, tarmoq uzildi, kodda xato throw → error boundary

6. Action'ni nomlash va props

React hujjati va kutubxonalar Action funksiyalarini …Action bilan nomlaydi: submitAction, updateNameAction. Komponent tashqaridan funksiya olsa va uni transition ichida chaqirsa — prop nomi ham action bilan tugaydi:

jsx
// Tugma bosilganda Action'ni o'zi transition ichida chaqiradi
function SaveButton({ saveAction, children }) {
  const [isPending, startTransition] = useTransition();
  return (
    <button
      disabled={isPending}
      onClick={() => startTransition(() => saveAction())}
    >
      {isPending ? "Saqlanmoqda…" : children}
    </button>
  );
}

Nom — kelishuv: saveAction ni ko'rgan dasturchi "bu transition ichida chaqiriladi, ichidagi setterlar Action tugagach chiziladi" deb biladi. Oddiy onClick prop'i esa bunday va'da bermaydi. Komponent API'sini loyihalash darsida bunday nomlarga qaytamiz.

7. Vazifalar qadami: yozuvlar — Action (1-qism)

7.1 Muammo: v7 dagi yozuv

vazifalar v7 da katak bosilganda shunday bo'lardi: flushSync bilan ro'yxat darhol yangilanadi, keyin serverga so'rov. Server rad etsa — oldingi ro'yxat qaytariladi. Bitta yozuvda bu ishlaydi. Ikki yozuv bir vaqtda bo'lsa-chi? Kanonni qurishda o'lchandi (07-poyga-v7.txt, soxta server, 1-katak rad etiladi, 2-katak tasdiqlanadi):

text
ekran ketma-ketligi (1-katak, 2-katak): -- → x- → xx → -- → -x

Ikki katak ketma-ket bosildi (xx). Birinchisi rad etildi va v7 "oldingi ro'yxat" ni qaytardi — u ikkinchi bosishdan oldingi edi. Ikkinchi katak ham ~400 ms yo'qoldi (--) va server javobidan keyin qaytdi. Foydalanuvchi uchun bu — "men belgilagan narsa o'chib qoldi".

Uch darsda shuni tuzatamiz: bugun — yozuv Action bo'ladi, keyingi darsda — forma, useOptimistic da — ekran va commit. Kanonda uchala dars — bitta commit (refactor/actions-optimistik branch'i).

7.2 fondaYoz — hodisadan Action

Katak, tahrir, o'chirish va import — oddiy hodisa ishlovchilari. Ular Action'ni o'zlari boshlaydi (App.tsx, kanondan aynan):

tsx
  // Hodisa ishlovchisidan (katak, tahrir, o'chirish, import):
  // action'ni o'zimiz boshlaymiz. Async funksiya startTransition
  // ichida — React 19
  function fondaYoz(
    amal: VazifaAmali,
    serverda: () => Promise<VazifaAmali>,
  ) {
    startTransition(async () => {
      await yozuv(amal, serverda);
    });
  }

startTransition — hook'siz versiyasi, react dan (useTransition dagi "hooksiz" bo'limi). isPending bu yerda kerak emas — katak darhol belgilanadi, kutish ko'rsatilmaydi. yozuv funksiyasining ichi — useOptimistic darsida.

E'tibor bering: serverda endi Promise<VazifaAmali> qaytaradi — server javobidan yasalgan amal. Masalan, yangi vazifa id'si endi serverdan keladi.

7.3 yozuvniYubor — tasdiq yana transition'da

Server qismi — server-sinxron.ts. Kod kanondan aynan:

ts
/** yozuvniYubor natijasi: rad etilsa — e'lon matni */
export type YozuvNatijasi =
  | { readonly ok: true }
  | { readonly ok: false; readonly xabar: string };

export async function yozuvniYubor(
  serverda: () => Promise<VazifaAmali>,
  s: Sozlagichlar,
  songgi: () => Vazifalar,
): Promise<YozuvNatijasi> {
  const x = s.xojalik.current;
  x.pult?.abort(); // yo'ldagi solishtirish endi eskirgan
  x.kutilmoqda++;
  let oxirgi = songgi();
  try {
    const tasdiq = await serverda();
    oxirgi = vazifalarReducer(
      boshlangichHolat(songgi()),
      tasdiq,
    ).vazifalar;
    // await'dan keyingi yangilanish yana startTransition ichida — u
    // action bilan BIRGA tugaydi: optimistik qatlam olinganda asosiy
    // holat allaqachon yangi (ekran bir zum eskisiga sakramaydi)
    startTransition(() => {
      s.dispatch(tasdiq);
    });
    tablargaYubor(oxirgi);
    return { ok: true };
  } catch (xato) {
    return { ok: false, xabar: `Saqlanmadi: ${xatoMatni(xato)}` };
  } finally {
    x.kutilmoqda--;
    // oxirgisi tugagach — server bilan solishtirish
    void sinxronla(oxirgi, s);
  }
}

Kanondagi JSDoc izohi bu yerda tushirib qoldirildi, oxirgi qatordagi izoh esa telefonga sig'ishi uchun yuqoriga ko'chirildi (u useOptimistic haqida — useOptimistic da o'qiymiz). Bugungi darsning uch qoidasi shu funksiyada:

  • await dan keyin — startTransition. s.dispatch(tasdiq) Action bilan birga tugaydi. O'ralmasa, u shoshilinch bo'lib alohida chizilardi — «await dan keyin — yana startTransition» bo'limidagi tuzoq.
  • Reject yo'q. Rad etilish — { ok: false, xabar } qiymati, throw emas. Rad etilgan yozuv kutilgan holat, ilovani yiqitmasligi kerak («Kutilgan xato — qiymat» bo'limi).
  • songgi() — funksiya, qiymat emas. await dan keyin closure'dagi ro'yxat eskirgan bo'lishi mumkin: yozuv yo'lda ekan, boshqa tab yoki boshqa yozuv ro'yxatni o'zgartirgan. songgi App'dagi ref'dan eng so'nggisini oladi (useRef I).

v7 dagi royxatniQoy(oldingi, s) — "oldingi ro'yxatni qaytarish" — yo'qoldi. Rad etilganda nima bo'ladi? Hech narsa: React o'zi qaytaradi. Qanday — useOptimistic darsining asosiy mavzusi.

7.4 flushSync ketdi, fokus qoldi

v7 da flushSync(() => dispatch(amal)) dan keyin darhol focus() chaqirilardi: DOM yangilangan bo'lishi kerak edi (useRef II). Action ichidagi yangilanishni flushSync bilan chizib bo'lmaydi — u transition. Kanondagi yechim: fokus rejalashtiriladi va keyingi commit'dan keyin useLayoutEffect da qo'yiladi (fokusniRejala). Kodini useOptimistic darsida to'liq o'qiymiz.

8. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
"Saqlanmoqda…" hech qachon ko'rinmaydi setter Action ichida, await dan oldin isPending, useFormStatus yoki useOptimistic
natija Action tugashidan oldin, alohida chiziladi await dan keyingi setter o'ralmagan startTransition(() => set…)
oq ekran, Uncaught Error Action throw qildi, error boundary yo'q kutilgani — qiymat; kutilmagani — Error boundary
xato bo'lsa ham maydon bo'shadi Action qiymat qaytardi — React uni muvaffaqiyat deb biladi useActionState + defaultValue (useActionState)
controlled maydon tozalanmadi reset faqat uncontrolled'ga tegadi Action ichida set…("")
requestFormReset was called outside a transition or action oddiy onClick da chaqirilgan startTransition ichiga
eski javob yangisini bosib ketdi Action'lar parallel, javoblar aralash tartibda useActionState (navbat) yoki so'nggi so'rovni tekshirish

9. Mashqlar

Mashqlar kurs/mashqlar/18/05-actions/ da. Har yechimni «Ishga tushir» bilan sinang.

1-mashq (oson): nima ko'rinadi?

Action shunday:

jsx
startTransition(async () => {
  setStatus("Yuborilmoqda…");
  await sendOrder();               // 300 ms
  startTransition(() => setStatus("Yuborildi"));
});
  • Bosilgandan 100 ms keyin status ekranda "Yuborilmoqda…" mi?
  • Bosilgandan 100 ms keyin isPending qanday?
  • Forma Action'i muvaffaqiyatli tugasa, controlled maydon bo'shaydimi?
  • requestFormReset ni oddiy onClick ichida (transition'siz) chaqirsa bo'ladimi?
Yechim
  • yo'q — setStatus("Yuborilmoqda…") Action ichida. Action yangilanishlari u tugagach birga chiziladi, "Yuborilmoqda…" ning ustidan "Yuborildi" yoziladi («"Saqlanmoqda…" ko'rinmadi» bo'limi).
  • true — isPending bosish paytidagi shoshilinch renderda true bo'ladi va Action tugaguncha shunday turadi.
  • yo'q — reset faqat uncontrolled maydonlarga tegadi; controlled maydon qiymati state'da.
  • yo'q — konsolda requestFormReset was called outside a transition or action chiqadi. Dev'da forma baribir tozalanadi, lekin reset Action'dan ajralib, alohida chiziladi.

2-mashq (o'rta): ikki tugma — ikki Action

«Bahor» buyurtma formasi: taom nomi (name="dish") va ikki tugma. «Savatga» — addToCart Action'i: "savat: Osh" qatorini ro'yxatga qo'shadi. «Hoziroq buyurtma» — orderNow Action'i: "buyurtma: Osh" qatorini qo'shadi. Ikkala Action ham FormData dan taom nomini oladi. onClick va preventDefault ishlatmang. Ishora: «formAction: bitta forma — ikki Action» bo'limi.

Yechim
jsx
import { useState } from "react";

export default function App() {
  const [log, setLog] = useState([]);

  function addToCart(formData) {
    setLog((l) => [...l, `savat: ${formData.get("dish")}`]);
  }

  function orderNow(formData) {
    setLog((l) => [...l, `buyurtma: ${formData.get("dish")}`]);
  }

  return (
    <form action={addToCart}>
      <label>
        Taom <input name="dish" />
      </label>
      <button>Savatga</button>{" "}
      <button formAction={orderNow}>Hoziroq buyurtma</button>
      <ul>
        {log.map((line, i) => (
          <li key={i}>{line}</li>
        ))}
      </ul>
    </form>
  );
}

Ekranda:

text
Taom Savatga Hoziroq buyurtma
savat: Osh
buyurtma: Manti

«Savatga» — formaning o'z action i, «Hoziroq buyurtma» — tugmaning formAction i. Har yuborishdan keyin maydon tozalanadi (uncontrolled), shuning uchun "Manti" ni yangidan yozamiz. setLog da updater funksiya — State II: bir nechta Action ketma-ket kelsa ham hech biri yo'qolmaydi.

3-mashq (qiyin): "Saqlanmoqda…" va xato

Narxni saqlash tugmasi. Boshlang'ich kod ishlaydi, lekin ikki muammo bor: "Saqlanmoqda…" hech qachon ko'rinmaydi va server rad etsa, ilova yiqiladi. Tuzating:

  1. Kutish paytida <p> da "Saqlanmoqda…" chiqsin (isPending bilan).
  2. Server rad etsa, ilova yiqilmasin: <p role="alert"> da xato matni chiqsin. Ishora: try/catch Action ichida, xatoni state'ga yozing. await dan keyingi setter — startTransition ichida.
  3. savePrice ni o'zgartirmang.
jsx
import { useState, useTransition } from "react";

function savePrice(price) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (price > 100000) reject(new Error("Narx juda katta"));
      else resolve(price);
    }, 200);
  });
}

export default function App() {
  const [message, setMessage] = useState("");
  const [, startTransition] = useTransition();

  function handleClick() {
    startTransition(async () => {
      setMessage("Saqlanmoqda…");
      const price = await savePrice(350000);
      startTransition(() => setMessage(`Saqlandi: ${price}`));
    });
  }

  return (
    <div>
      <button onClick={handleClick}>Saqlash</button>
      <p>{message}</p>
    </div>
  );
}

Konsolda:

text
Uncaught Error: Narx juda katta
Yechim
jsx
import { useState, useTransition } from "react";

function savePrice(price) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (price > 100000) reject(new Error("Narx juda katta"));
      else resolve(price);
    }, 200);
  });
}

export default function App() {
  const [message, setMessage] = useState("");
  const [error, setError] = useState(null);
  const [isPending, startTransition] = useTransition();

  function handleClick() {
    startTransition(async () => {
      setError(null);
      try {
        const price = await savePrice(350000);
        startTransition(() => setMessage(`Saqlandi: ${price}`));
      } catch (err) {
        startTransition(() => setError(err.message));
      }
    });
  }

  return (
    <div>
      <button onClick={handleClick} disabled={isPending}>
        Saqlash
      </button>
      <p>{isPending ? "Saqlanmoqda…" : message}</p>
      {error && <p role="alert">{error}</p>}
    </div>
  );
}

Ekranda:

text
Saqlash
Narx juda katta

"Saqlanmoqda…" endi isPending dan — u shoshilinch renderda chiziladi (ekran("Saqlanmoqda…") 60 ms da tekshiradi). Xato catch da qiymatga aylandi. Action reject bo'lmaydi va React uni error boundary'ga yubormaydi. setError ham await dan keyin, shuning uchun u ham startTransition ichida. 350000 ni 35000 ga almashtirsangiz — "Saqlandi: 35000". Bunday holatda useActionState qisqaroq — keyingi darsda.

4-mashq: Vazifalar qadami

refactor/actions-optimistik branch'ini oching. server-sinxron.ts dagi yozuvniYubor ni «Vazifalar qadami» bo'limidagidek o'zgartiring (serverda — Promise<VazifaAmali>, tasdiq — startTransition ichida, natija — YozuvNatijasi). App'da fondaYoz ni qo'shing. Commit — uchinchi qismdan keyin (useOptimistic).

Yechim

Tekshiruv ro'yxati:

  • yozuvniYubor hech qachon reject bo'lmaydi: catch — { ok: false, xabar }, finally — kutilmoqda-- va solishtirish.
  • await serverda() dan keyingi s.dispatch(tasdiq) — startTransition(() => …) ichida.
  • fondaYoz — startTransition(async () => { await yozuv(amal, serverda); }); yozuv ni useOptimistic darsida yozasiz, hozircha tsc uni topmaydi — bu kutilgan.
  • Hali commit qilmang: kanonda #05–#07 — bitta commit.

10. Real ishda

  • React hujjati va kutubxonalar — yangi kodda "kutish" holati endi isPending dan, isLoading state'idan emas. Kod ko'rigida qo'lda setIsSaving(true) / finally ni ko'rsangiz — Action'ga aylantirishni taklif qilish mumkin.
  • Next.js Server Actions — <form action={serverFunction}>: Action serverda bajariladi, React esa bugungi qoidalar bilan uni kutadi va formani tozalaydi (Server Actions I). Bugungi dars — ularning brauzer tomoni.
  • TanStack Query dagi useMutation — isPending, xato va qayta urinish bilan yozuvlar. Action g'oyasi unga juda yaqin (TanStack Query III).
  • Intervyu savollari: "React 19 da Action nima?", "<form action> nima qiladi va qachon formani tozalaydi?", "nega Action ichidagi setState darhol ko'rinmaydi?", "requestFormReset va form.reset() farqi?".

Xulosa

  • Action — transition ichida chaqirilgan funksiya (ko'pincha async). isPending u tugaguncha true.
  • Action ichidagi setterlar u tugagach birga chiziladi — oraliq "Saqlanmoqda…" ko'rinmaydi. await dan keyingi setter — yana startTransition.
  • <form action> va formAction — Action'ning tayyor yo'li: FormData, transition va muvaffaqiyatda uncontrolled maydonlarni reset.
  • requestFormReset(form) — faqat Action ichida; reset Action natijasi bilan birga chiziladi.
  • Action'dagi throw — error boundary'ga; kutilgan xato — qiymat.
  • vazifalar: fondaYoz — hodisadan Action, yozuvniYubor — reject'siz, tasdiq startTransition ichida (1-qism, commit — #07).

Keyingi dars: useActionState va useFormStatus — Action natijasi va navbati, forma ichidagi tugma o'z formasining holatini props'siz qanday bilishi.

Manbalar

  • React: useTransition — "Perform non-blocking updates with Actions" — react.dev/reference/react/useTransition (2026-10 holati, React 19.3)
  • React: <form> — react.dev/reference/react-dom/components/form
  • React: requestFormReset — react.dev/reference/react-dom/requestFormReset
  • React blog: "React v19" (2024-12-05) — Actions — react.dev/blog/2024/12/05/react-19
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Actions: async transitionlar va form action — React 19 da kutish, xato va forma reset — IlmHamroh