IlmHamroh
JavaScript Full-stack/20-qism. React: UI kutubxonalar va animatsiya9/30-dars16 daqiqa
Mundarija (24)

Toast bildirishnomalar React'da: sonner, aria-live va qachon toast kerak emas

Qisqacha: Toast — ekran burchagida bir necha soniya ko'rinib, o'zi yo'qoladigan qisqa xabar: "Saqlandi", "Nusxa olindi". React'da eng ommabopi — sonner: ildizga bitta <Toaster />, istalgan joydan toast.success("…"). Toast ishni to'xtatmaydi — shuning uchun forma xatosi uchun (inline xato kerak) va xavfli amal tasdig'i uchun (modal kerak) u noto'g'ri tanlov. Ekran o'quvchi uchun toast — aria-live hudud. sonner o'z CSS'ini <style> tegi bilan qo'shadi, shuning uchun vazifalar o'z toast'ida qoldi.

Bu darsda

  • sonner bilan success, error, toast.promise va «Bekor qilish» (action) li toastlarni jonli yasaysiz.
  • Toast ekran o'quvchiga qanday yetishini (aria-live, role="status") va klaviatura bilan unga qanday borilishini ko'rasiz.
  • Toast, inline xato va modal oyna orasidagi chegarani aniq qoidalar bilan aytasiz.
  • react-hot-toast va react-toastify'ni bilasiz, ularning CSP bilan munosabatini o'lchov va manba kodidan o'qiysiz.
  • vazifalar nega sonner emas, o'z toast'ini ishlatishini kanon raqamlari bilan tushuntirasiz.

Oldin bilishingiz kerak: Jonli hududlar va dinamik kontent, Tooltip, dropdown va toast bildirishnomalar, Portallar, shadcn/ui II.

1. Nega bu kerak?

Jasur aka kassada buyurtma saqladi. Ekranda hech narsa o'zgarmadi — saqlandimi yoki yo'qmi? U yana bosdi. Ikki buyurtma bo'ldi.

Har amaldan keyin foydalanuvchi javob kutadi. Ba'zi javoblar sahifaning o'zida ko'rinadi (ro'yxatga yangi qator qo'shildi). Ba'zilari esa ko'rinmaydi: "server saqladi", "havola nusxalandi", "fon rejimida yuborildi". Ana shular uchun toast.

CSS toast darsida toast'ni popover bilan qurgan edik, Portallar darsida esa vazifalar ga o'z toast'imiz — Bildirishnoma qo'shilgan. Bugun React ekotizimidagi tayyor yechimlarni ko'ramiz va eng muhim savolga javob beramiz: toast qachon kerak.

Hayotiy o'xshatish: toast — ofitsiantning "buyurtmangiz oshxonaga ketdi" degan qisqa gapi. U sizni to'xtatmaydi, javob kutmaydi. Agar sizdan javob kerak bo'lsa ("osh tugagan, manti olasizmi?"), ofitsiant to'xtab, savol beradi — bu endi modal oyna.

2. sonner: bitta <Toaster />, istalgan joydan toast()

2.1 Birinchi toast

sonner (2.0.8) — shadcn/ui'ning toast komponenti aynan shu (shadcn/ui II dagi Sonner). Ishlash tartibi ikki qadam:

  1. Ilova ildiziga bitta <Toaster /> — toastlar shu yerda chiziladi.
  2. Istalgan joydan — toast("matn"), hatto komponentdan tashqarida ham.

Natija oynasida sonner bor:

jsx
import { Toaster, toast } from "sonner";

function showError() {
  toast.error("Server javob bermadi", {
    description: "Qayta urinib ko'ring",
  });
}

export default function App() {
  return (
    <div>
      <button onClick={() => toast.success("Bron saqlandi")}>
        Saqlash
      </button>{" "}
      <button onClick={showError}>Xato</button>
      <Toaster richColors />
    </div>
  );
}

Ekranda:

text
Saqlash Xato
Server javob bermadi
Qayta urinib ko'ring
Bron saqlandi

«Ishga tushir» bilan bosib ko'ring: toastlar o'ng pastda ustma-ust chiqadi, bir necha soniyadan keyin yo'qoladi. Ekran matnida tartibga qarang: yangisi tepada — sonner oxirgi toast'ni ro'yxat boshiga qo'yadi.

  • toast.success, toast.error — turi (ikonka va rang). Yana toast.info, toast.warning, toast.loading va oddiy toast().
  • description — sarlavha ostidagi ikkinchi qator.
  • richColors — success yashil, error qizil fonda. Bermasangiz — hammasi bir xil oq fon, farq faqat ikonkada.

<Toaster /> ning boshqa sozlamalari: position ("top-center", "bottom-right"…), duration (ms, standart 4000), closeButton, expand (ustma-ust emas, ochiq ro'yxat), theme ("light", "dark", "system").

2.2 toast.promise: yuklanish → natija

Server so'rovi uchun eng qulay shakl. Bitta toast holatini o'zi almashtiradi:

jsx
import { Toaster, toast } from "sonner";

// Soxta server: 300 ms dan keyin buyurtma raqami
const sendOrder = () =>
  new Promise((resolve) => {
    setTimeout(() => resolve({ id: 12 }), 300);
  });

function submit() {
  toast.promise(sendOrder(), {
    loading: "Yuborilmoqda…",
    success: (order) => `Buyurtma №${order.id} qabul qilindi`,
    error: "Yuborib bo'lmadi",
  });
}

export default function App() {
  return (
    <div>
      <button onClick={submit}>Yuborish</button>
      <Toaster />
    </div>
  );
}

Ekranda:

text
Yuborish
Buyurtma №12 qabul qilindi

Bosilgan zahoti toast «Yuborilmoqda…» edi (bu oraliq holatni biz ham tekshirdik). Promise bajarilgach o'sha toast muvaffaqiyat matniga almashdi. success — funksiya: promise natijasini (order) oladi. Promise rad etilsa (reject), error matni chiqadi (Promise asoslari darsidagi uch holat — kutish, bajarildi, rad etildi — toast ko'rinishida).

2.3 action: «Bekor qilish»

O'chirishdan oldin "ishonchingiz komilmi?" deb so'rash o'rniga — darhol o'chirib, toast'da «Bekor qilish» tugmasini berish. Gmail "Xat o'chirildi. Bekor qilish" shunday ishlaydi.

jsx
import { useState } from "react";
import { Toaster, toast } from "sonner";

export default function App() {
  const [items, setItems] = useState(["Osh", "Manti"]);
  function remove() {
    setItems(["Manti"]);
    toast("Osh o'chirildi", {
      action: {
        label: "Bekor qilish",
        onClick: () => setItems(["Osh", "Manti"]),
      },
    });
  }
  return (
    <div>
      <p>{items.join(", ")}</p>
      <button onClick={remove}>O'chirish</button>
      <Toaster />
    </div>
  );
}

Ekranda:

text
Osh, Manti
O'chirish

Bosh holat qaytdi. Avval «O'chirish»: Osh yo'qoldi, toast chiqdi. Keyin klaviatura bilan «Bekor qilish»: Osh qaytdi, toast yopildi. Klaviatura yo'lini o'zingiz sinang — bu Bobur (faqat klaviatura) yo'li:

Qadam Fokus Ekranda
«O'chirish» «O'chirish» tugmasi «Manti», toast «Osh o'chirildi»
Tab toast'ning o'zi (sonner har toast'ga tabindex="0" beradi) o'zgarmaydi
Tab «Bekor qilish» o'zgarmaydi
Enter — «Osh, Manti», toast yopildi

<Toaster /> DOM'da tugmadan keyin turgani uchun Tab unga yetdi.

Diqqat: Toast'dagi tugma bilan ikki muammo bor. Birinchisi — vaqt. Toast 4 soniyada yo'qoladi: sekin o'qiydigan yoki klaviatura bilan yuradigan odam ulgurmasligi mumkin. sonner sichqoncha ustida turganda va fokus toast ichida bo'lganda taymerni to'xtatadi, lekin bunga yetib borish kerak. Ikkinchisi — joy: toast DOM oxirida, Bobur unga uzoq Tab bosib boradi. sonner'da buning uchun yorliq bor — Alt+T (quyidagi aria-label da ko'rasiz). Shuning uchun bekor qilish imkoniyati faqat toast'da qolmasin: masalan, "O'chirilganlar" bo'limi ham bo'lsin.

3. Toast va ekran o'quvchi

3.1 sonner ichida nima bor

Toast ko'z uchun burchakda. Malika esa fokusdagi elementni eshitadi — burchakdagi yangi matnni u qanday biladi? Jonli hududlar darsidagi javob: aria-live. sonner'ning DOM'ini o'qiymiz:

jsx
import { Toaster, toast } from "sonner";

function inspect() {
  setTimeout(() => {
    const region = document.querySelector("section");
    for (const name of ["aria-live", "aria-label", "aria-relevant"]) {
      console.log(name, "=", region.getAttribute(name));
    }
    const item = document.querySelector("[data-sonner-toast]");
    console.log("toast tabindex =", item.getAttribute("tabindex"));
  }, 200);
}

export default function App() {
  return (
    <div>
      <button
        onClick={() => {
          toast.success("Saqlandi");
          inspect();
        }}
      >
        Saqlash
      </button>
      <Toaster />
    </div>
  );
}

Konsolda:

text
aria-live = polite
aria-label = Notifications alt+T
aria-relevant = additions text
toast tabindex = 0
  • aria-live="polite" — yangi toast matni ekran o'quvchida joriy gap tugagach o'qiladi. Muhim: toast.error ham polite — sonner xatoni ham shoshiltirmaydi.
  • aria-label="Notifications alt+T" — hudud nomi va yorliq: Alt+T toastlar ro'yxatiga fokusni olib boradi. Nom inglizcha — o'zbekcha ilovada <Toaster containerAriaLabel="Bildirishnomalar" /> bilan almashtiring.
  • aria-relevant="additions text" — faqat qo'shilgan toastlar va matn o'zgarishi o'qiladi, yo'qolganlari emas.
  • tabindex="0" — har toast Tab bilan fokus oladi (oldingi bo'limdagi Bobur yo'li).

3.2 Asosiy qoida: hudud oldindan bo'lsin

Jonli hududlar darsidagi qoida React'da ayniqsa muhim: jonli hudud DOM'da xabardan oldin bo'lishi kerak. Ekran o'quvchi mavjud hududdagi o'zgarishni kuzatadi. Agar hudud xabar bilan birga paydo bo'lsa, ko'p ekran o'quvchi uni o'qimaydi.

React'da ko'p uchraydigan xato:

jsx
{message && <p role="status">{message}</p>}

message bo'sh bo'lganda <p> yo'q. Xabar kelganda <p> matni bilan birga tug'iladi — hudud yangi, kuzatilmagan.

Hudud doim turadi, faqat matni o'zgaradi:

jsx
<p role="status">{message}</p>

sonner'da <section aria-live> <Toaster /> chizilganda paydo bo'ladi — birinchi toast'dan oldin. Kanon Bildirishnoma ham shunday: .bildirishnoma:empty { opacity: 0; } — bo'sh bo'lsa ko'rinmaydi, lekin DOM'da.

Tekshirib ko'ring: Nega bo'sh toast'ni display: none yoki hidden bilan yashirmaymiz, opacity: 0 bilan yashiramiz?

Javob

display: none va hidden elementni ekran o'quvchining daraxtidan ham olib tashlaydi. Keyin matn bilan birga yana ko'rinsa — bu xuddi "yangi tug'ilgan" hudud, o'zgarish o'qilmasligi mumkin. opacity: 0 faqat ko'zdan yashiradi: element daraxtda qoladi va kuzatiladi. Kanon CSS izohi ham shuni aytadi: "hidden yoki display: none bo'lsa, ekran o'quvchi keyingi matnni o'qimasligi mumkin".

4. Toast, inline xato yoki modal?

Bu darsning eng muhim qismi. Toast — bloklamaydigan, o'tkinchi, ixtiyoriy xabar. Shu uch so'z chegarani belgilaydi:

flowchart TD
  A{"Foydalanuvchidan<br/>javob kerakmi?"} -->|ha| M["Modal / dialog"]
  A -->|yo'q| B{"Xabar aniq bir<br/>maydonga tegishlimi?"}
  B -->|ha| I["Inline xato<br/>maydon yonida"]
  B -->|yo'q| C{"Uni o'tkazib yuborsa<br/>ziyon bormi?"}
  C -->|ha| P["Sahifadagi doimiy xabar<br/>(banner)"]
  C -->|yo'q| T["Toast"]

Nimaga qarang: toast — daraxtning eng oxirgi bargi. Avval boshqa uch savoldan o'tadi.

Vaziyat To'g'ri Nega toast emas
Telefon raqami formati xato inline xato (aria-describedby) xato maydonga tegishli; toast 4 s da yo'qoladi, odam qaysi maydonni tuzatishni unutadi
"Vazifa o'chirilsinmi?" modal / <dialog> javob kerak
Internet uzildi doimiy banner holat davom etadi, o'tkinchi emas
"Bron saqlandi" toast natija tasdig'i, javob kerak emas
"Havola nusxalandi" toast ko'rinmas amalning tasdig'i
O'chirildi + bekor qilish toast + action (va boshqa yo'l ham) qaytariladigan amal

Ko'p uchraydigan xato — "hamma xato toast'da". Forma yuborildi, server 5 ta maydonda xato qaytardi, ekranda 5 ta qizil toast. Ular yo'qoladi, Aziz esa qaysi maydon ekanini bilmay qoladi. To'g'risi — har xato o'z maydoni yonida (RHF + Zod darsidagi aria-invalid va aria-describedby), toast esa bitta umumiy gap bilan: "Formada 5 ta xato bor".

Tekshirib ko'ring: "Sessiya muddati tugadi, qaytadan kiring" — toast'mi?

Javob

Yo'q. Foydalanuvchi bundan keyin ishlay olmaydi — amal kerak (qayta kirish). Toast yo'qolsa, u nega hech narsa saqlanmayotganini tushunmaydi. To'g'risi — modal oyna (yoki kirish sahifasiga yo'naltirish) aniq tugma bilan. "O'tkazib yuborsa ziyon bormi?" savoliga javob — ha.

5. Boshqa toast kutubxonalari

5.1 react-hot-toast

react-hot-toast (2.6.1) — kichik, API'si sonner'ga o'xshaydi (toast.success, toast.promise, <Toaster />). Uslubini goober (kichik CSS-in-JS) bilan beradi. Kanon tajribasida (kitlar-ishlat-toast-sonner-toast-hot.txt) — native <dialog> + react-hot-toast varianti vazifalar CSP'si ostida:

text
{"v":"toast-hot","xato":"Waiting failed: 10000ms exceeded","csp":["require-trusted-types-for trusted-types-sink"],"konsol":["This document requires 'TrustedHTML' assignment. The action has been blocked.","pageerror: Failed to set the 'innerHTML' property on 'Element': This document requires 'TrustedHTML' assignment.\nObject.assign (<anonymous>)"]}

Ilova ochilmadi: goober uslubni innerHTML bilan yozadi, Trusted Types to'sadi — Ant Design va Mantine bilan bir xil sabab. Kanon raqami (kanon qaydi, 4-qaror): +4.5 KB.

5.2 react-toastify

react-toastify (11.1.0) — eng eski va ko'p sozlamali kutubxonalardan biri: toast.success, <ToastContainer />, progress chizig'i, sudrab yopish. Kanon o'lchamagan. Paketni o'rnatib, ichini ko'rdik (2026-10-17):

  • standart kirish (react-toastify) — CSS'ni document.createElement("style") bilan o'zi qo'shadi (dist/index.mjs da bitta shunday joy);
  • react-toastify/unstyled — xuddi shu komponentlar, lekin <style> yasamaydi (unstyled.js da 0 ta), CSS esa alohida faylda: react-toastify/dist/ReactToastify.css.

Ya'ni CSP'li loyiha uchun yo'l bor: unstyled kirish + CSS faylni import qilish. Bu "kutubxona sizning siyosatingizga moslashadi" tamoyilining yaxshi namunasi. Baribir — o'z ilovangizda CSP ostida sinamaguncha "ishlaydi" demang.

5.3 sonner'ning <style> i — natija oynasida ko'ramiz

sonner'chi? Natija oynasida ikki blokni solishtiramiz. Avval faqat React:

jsx
const count = document.querySelectorAll("style").length;
console.log("<style>:", count);

export default function App() {
  return <p>Faqat React</p>;
}

Konsolda:

text
<style>: 1

Bittasi — natija oynasining o'z uslubi. Endi sonner import qilingan blok:

jsx
import { Toaster } from "sonner";

const styles = [...document.querySelectorAll("style")];
console.log("<style>:", styles.length);
console.log(styles[1].textContent.slice(0, 31));

export default function App() {
  return <Toaster />;
}

Konsolda:

text
<style>: 2
[data-sonner-toaster][dir=ltr],

Ikkinchi teg — sonner'ning CSS'i: u import paytida JavaScript bilan <style> yasab, <head> ga qo'shadi. Bitta ham toast ko'rsatilmasdan. Natija oynasida inline <style> ga ruxsat bor — ishladi. vazifalar ning style-src 'self' siyosatida esa bu teg to'siladi: kanon o'lchovida 2 ta buzilish (style-src-elem inline ×2), toast chiqdi, lekin uslubsiz.

Maslahat: Natija oynasida Radix yoki lucide import qilingan bloklarda ham <style> 2 ta bo'ladi — Radix, cmdk, sonner, lucide, clsx va cva natija oynasiga bitta to'plamda yuklanadi — sonner'ning CSS'i ham birga keladi. Shuning uchun Radix chuqur darsida "oldin" qiymati 2 edi.

6. Biz sinab ko'rdik: nega vazifalar da sonner yo'q

Bu darsda vazifalar kodi o'zgarmaydi — o'lchov darsi. Kanon tasdiq oynasi + toast ekranini uch xil toast bilan o'lchadi (Chrome 155, vazifalar CSP'si):

Variant Hajm (kanon qaydi) CSP Natija
o'z toast (kanon) 0 KB 0
sonner 2.0.8 +8.9 KB 2 (style-src-elem) toast chiqdi, uslub to'sildi
react-hot-toast 2.6.1 +4.5 KB Trusted Types ochilmadi

Faqat React'ga nisbatan o'lchovda (kutubxonalar.txt) sonner — +9.6 KB va o'sha 2 buzilish.

Kanon qarori (20-qism qarorlari, 2-qaror): "Tasdiq oynasi native <dialog>, toast — o'zimizniki — QABUL. Sabab CSP". Bizning toast — 18-qismdan beri bor (Portallar):

tsx
// Bildirishnoma.tsx (kanon, qisqartirilgan)
export function Bildirishnoma({ matn }: Props) {
  // role="status" — jonli hudud doim DOM'da (bo'sh bo'lsa ham)
  return createPortal(
    <p id="elon" role="status" className="bildirishnoma">
      {matn}
    </p>,
    document.body,
  );
}

Matn 3 soniyadan keyin o'chadi (ilova-holati.ts, ELON_MUDDATI = 3000, Zustand store'da). CSS — oddiy fayl, position: fixed, pointer-events: none (18-qismda e2e topgan: toast ostidagi tugmani yopib qo'ygan edi).

Nima yo'qotdik? sonner'ning ustma-ust toastlari, toast.promise, action, sudrab yopish, chiroyli animatsiya. vazifalar ga hozircha bular kerak emas: e'lonlar qisqa, bittadan. Kerak bo'lsa — o'zimiz qo'shamiz (3-mashqda «Bekor qilish» ni yozasiz) yoki sonner'ni CSP'ga mos yo'l bilan ulaymiz.

sonner qachon to'g'ri tanlov: shadcn loyihasi, CSP'siz yoki serverli nonce bilan; bir nechta toast ustma-ust, promise va action kerak bo'lganda. Bu holatlarda sonner — eng qulay variant.

7. Ko'p uchraydigan xatolar

Belgi Sabab Davosi
toast chiqmadi, xato yo'q <Toaster /> ilovada yo'q ildizga bitta <Toaster />
har toast ikki marta ikkita <Toaster /> (layout va sahifada) faqat bittasi
ekran o'quvchi xabarni o'qimaydi hudud xabar bilan birga tug'iladi hudud doim DOM'da, matn o'zgaradi
forma xatolari toast'da yo'qoladi xato maydonga tegishli inline xato + aria-describedby
Applying inline style violates … 'style-src 'self'' sonner <style> qo'shadi CSP'ga mos variant yoki o'z toast
This document requires 'TrustedHTML' assignment react-hot-toast (goober) innerHTML boshqa kutubxona
toast tugmani yopib qo'ydi, bosilmaydi toast pointer-events tugmasiz toast'ga pointer-events: none

8. Mashqlar

Mashqlar kurs/mashqlar/20/09-toast/ da.

1-mashq (oson): qaysi biri?

Har vaziyatga bitta so'z: toast, inline, modal yoki banner.

  • Parol 8 belgidan qisqa:
  • "Rasm yuklandi":
  • "Butun menyu o'chirilsinmi?":
  • Server texnik ishlar sababli 30 daqiqa yopiq:
Yechim
  • Parol — maydonga tegishli xato: inline, maydon ostida, aria-describedby bilan.
  • "Rasm yuklandi" — javob kerak emas, o'tkazib yuborsa ziyon yo'q: toast.
  • Butun menyuni o'chirish — javob kerak, xavfli: modal.
  • Texnik ishlar — holat davom etadi: banner, sahifada doimiy.

2-mashq (o'rta): toast.promise bilan saqlash

«Saqlash» tugmasi soxta server funksiyasini chaqiradi. Toast: yuklanishda «Saqlanmoqda…», muvaffaqiyatda «Taom saqlandi: Manti», xatoda «Saqlab bo'lmadi». Server 300 ms kutadi va taom nomini qaytaradi. Ekranda oxirida tugma va toast qolsin.

Ishora: «toast.promise» bo'limi; success — funksiya.

text
Saqlash
Taom saqlandi: Manti
Yechim
jsx
import { Toaster, toast } from "sonner";

const saveDish = (name) =>
  new Promise((resolve) => setTimeout(() => resolve(name), 300));

function save() {
  toast.promise(saveDish("Manti"), {
    loading: "Saqlanmoqda…",
    success: (name) => `Taom saqlandi: ${name}`,
    error: "Saqlab bo'lmadi",
  });
}

export default function App() {
  return (
    <div>
      <button onClick={save}>Saqlash</button>
      <Toaster />
    </div>
  );
}

Ekranda:

text
Saqlash
Taom saqlandi: Manti

Oraliq holat — «Saqlanmoqda…» — bosilgan zahoti ko'rinadi. saveDish ni reject qiladigan qilib o'zgartirib ko'ring: toast «Saqlab bo'lmadi» bo'ladi. Haqiqiy loyihada bu yerda mutatsiya bo'lardi va toast.promise ga uning promise'i beriladi.

3-mashq (qiyin): o'z toast'ingizga «Bekor qilish»

Kanon Bildirishnoma ruhida, kutubxonasiz:

  1. Toast komponenti — role="status" li <div> doim DOM'da; ichida matn va (bo'lsa) «Bekor qilish» tugmasi.
  2. «O'chirish» — ro'yxatdan «Osh» ni olib tashlaydi va toast'ga «Osh o'chirildi» + bekor qilish amalini beradi.
  3. «Bekor qilish» — ro'yxatni tiklaydi va toast'ni bo'shatadi.
  4. Toast 3 000 ms dan keyin o'zi bo'shaydi (taymer — useEffect, cleanup bilan).

O'chirib, bekor qilgandan keyin ekran:

text
Osh, Manti
O'chirish

Ishora: holat { text, undo } yoki null; taymer — effekt va cleanup.

Yechim
jsx
import { useEffect, useState } from "react";

function Toast({ toast, onClose }) {
  useEffect(() => {
    if (toast === null) return;
    const timer = setTimeout(onClose, 3000);
    return () => clearTimeout(timer);
  }, [toast, onClose]);
  return (
    <div role="status" className="toast">
      {toast !== null && (
        <>
          {toast.text}{" "}
          {toast.undo && (
            <button
              onClick={() => {
                toast.undo();
                onClose();
              }}
            >
              Bekor qilish
            </button>
          )}
        </>
      )}
    </div>
  );
}

export default function App() {
  const [items, setItems] = useState(["Osh", "Manti"]);
  const [toast, setToast] = useState(null);
  function remove() {
    const before = items;
    setItems(items.filter((i) => i !== "Osh"));
    const undo = () => setItems(before);
    setToast({ text: "Osh o'chirildi", undo });
  }
  return (
    <div>
      <p>{items.join(", ")}</p>
      <button onClick={remove}>O'chirish</button>
      <Toast toast={toast} onClose={() => setToast(null)} />
    </div>
  );
}

Ekranda:

text
Osh, Manti
O'chirish

role="status" li <div> doim bor — faqat ichi o'zgaradi («Hudud oldindan bo'lsin»). before — o'chirishdan oldingi ro'yxat, closure ichida saqlanadi (Closure). Taymer har yangi toast'da qayta boshlanadi: toast o'zgarganda effekt cleanup eski taymerni to'xtatadi — aks holda eski taymer yangi toast'ni muddatidan oldin yopardi (eskirgan closure va effekt cleanup). Bir kamchilik: onClose har renderda yangi funksiya — effekt har renderda qayta ulanadi. Kanondagi kabi useEffectEvent (Effekt hodisalari) yoki useCallback bilan tuzatish mumkin.

4-mashq: tajriba qadami (ixtiyoriy)

vazifalar ga tegmang. kurs/tajriba/toast/ da Vite + React loyiha: bitta tugma va sonner. index.html ga vazifalar dagi CSP <meta> ini qo'shing, DevTools konsolida buzilishlarni sanang. Keyin <Toaster /> ni olib tashlab (faqat import { toast } from "sonner" qoldirib) yana sanang.

Yechim
  • <Toaster /> bilan: konsolda Applying inline style violates the following Content Security Policy directive 'style-src 'self''… — kanon o'lchovidagi 2 ta buzilishga o'xshash natija; toast chiqadi, lekin joylashuvi va ko'rinishi buzilgan.
  • <Toaster /> siz ham buzilish qoladi: sonner CSS'ni import paytida qo'shadi («sonner'ning <style> i» bo'limi), toast ko'rsatilishini kutmaydi.
  • Xulosa yozing: "sonner — import qilingan zahoti <style> yasaydi; style-src 'self' bilan ishlashi uchun nonce yoki boshqa yechim kerak".

9. Real ishda

  • shadcn loyihalari — sonner standart: components/ui/sonner.tsx o'rami, ildizda <Toaster />. Server javoblari uchun toast.promise yoki TanStack Query'ning onSuccess/onError ichida toast.
  • Kod ko'rigi — "bu xato toast'da bo'lishi kerakmi?" degan savol tez-tez beriladi. Javob — «Toast, inline xato yoki modal?» bo'limidagi daraxt.
  • Intervyu savollari: "toast va modal farqi?", "aria-live polite va assertive qachon?", "nega jonli hudud oldindan DOM'da bo'lishi kerak?", "kutubxona CSP bilan ishlashini qanday tekshirasiz?".

Xulosa

  • Toast — bloklamaydigan, o'tkinchi, ixtiyoriy xabar; javob kerak bo'lsa — modal, maydon xatosi — inline, davomli holat — banner.
  • sonner: ildizda bitta <Toaster />, istalgan joydan toast.success/error/promise, action bilan «Bekor qilish», richColors, position.
  • Ekran o'quvchi uchun — aria-live="polite" hudud; hudud xabardan oldin DOM'da bo'lsin (opacity: 0, display: none emas).
  • sonner CSS'ni import paytida <style> bilan qo'shadi (vazifalar CSP'sida 2 buzilish), react-hot-toast Trusted Types'da ochilmaydi, react-toastify'da unstyled + CSS fayl yo'li bor.
  • vazifalar — o'z toast'i: role="status", portal, 3 s, pointer-events: none.

Keyingi dars: Modal, drawer, popover va tooltip — qatlamli UI'ning to'rt turi: native <dialog> va Radix Dialog, vaul drawer, Floating UI'ning offset/flip/shift i, URL bilan modal — va vazifalar ga Radix tooltip.

Manbalar

  • sonner — sonner.emilkowal.ski (2.0.8): Toaster, toast.promise, action
  • react-hot-toast — react-hot-toast.com (2.6.1); react-toastify — fkhadra.github.io/react-toastify (11.1.0)
  • WAI-ARIA: aria-live, role="status" — w3.org/TR/wai-aria
  • Nielsen Norman Group: Indicators, Validations, and Notifications — nngroup.com
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Toast bildirishnomalar React'da: sonner, aria-live va qachon toast kerak emas — IlmHamroh