Update Prompt UI
กลยุทธ์ การ register แบบ prompt
หัวข้อที่มีชื่อว่า “กลยุทธ์ การ register แบบ prompt”เมื่อตั้งค่า registerType เป็น 'prompt' นั้น vite-plugin-pwa จะไม่เรียก skipWaiting() โดยอัตโนมัติ แต่จะให้ hook useRegisterSW() จาก virtual module virtual:pwa-register/react แทน เพื่อให้คุณสร้าง update UI ของตัวเองได้ วิธีนี้ให้ผู้ใช้ควบคุมว่าจะให้ service worker ใหม่ activate และโหลดหน้าใหม่เมื่อใด
hook useRegisterSW
หัวข้อที่มีชื่อว่า “hook useRegisterSW”ดึง hook จาก virtual module ที่ vite-plugin-pwa เปิดเผยในช่วง build time:
import { useRegisterSW } from 'virtual:pwa-register/react';hook นี้คืนค่าสามอย่าง:
needRefresh— tuple แบบ[boolean, setter]ค่า boolean จะเป็นtrueเมื่อ service worker ใหม่ติดตั้งแล้วและกำลังรอ activateofflineReady— tuple แบบ[boolean, setter]ค่า boolean จะเป็นtrueเมื่อ asset ทั้งหมดของแอปถูก cache ครบแล้วและแอปสามารถทำงานแบบออฟไลน์ได้updateServiceWorker(reloadPage?: boolean)— เรียกด้วยtrueเพื่อส่งskipWaiting()ไปยัง service worker ที่กำลังรอและโหลดหน้าใหม่
component ReloadPrompt
หัวข้อที่มีชื่อว่า “component ReloadPrompt”component ด้านล่างอ่านค่าทั้งสามนั้นและ render toast banner เฉพาะเมื่อมีสิ่งที่ต้องแจ้งผู้ใช้
import { useRegisterSW } from 'virtual:pwa-register/react';
export function ReloadPrompt() { const { offlineReady: [offlineReady, setOfflineReady], needRefresh: [needRefresh, setNeedRefresh], updateServiceWorker, } = useRegisterSW();
if (!offlineReady && !needRefresh) { return null; }
return ( <div style={{ position: 'fixed', bottom: '1rem', right: '1rem', padding: '1rem', background: '#1e1e2e', color: '#cdd6f4', borderRadius: '0.5rem', boxShadow: '0 2px 8px rgba(0,0,0,0.4)', zIndex: 9999, }} > {offlineReady ? ( <p>App ready to work offline.</p> ) : ( <p>New content available, click Reload to update.</p> )}
{needRefresh && ( <button onClick={() => updateServiceWorker(true)}>Reload</button> )}
<button onClick={() => { setOfflineReady(false); setNeedRefresh(false); }} > Close </button> </div> );}เชื่อมต่อกับแอป
หัวข้อที่มีชื่อว่า “เชื่อมต่อกับแอป”นำ ReloadPrompt ไป import ใน App.tsx ของคุณและ render ไว้ที่ส่วนล่างสุดของ component tree เพื่อให้ prompt ลอยอยู่เหนือเนื้อหาอื่นทั้งหมด:
import { ReloadPrompt } from './ReloadPrompt';
export default function App() { return ( <> {/* your app content */} <ReloadPrompt /> </> );}TypeScript type shim
หัวข้อที่มีชื่อว่า “TypeScript type shim”หาก TypeScript รายงานว่าไม่พบ module virtual:pwa-register/react ให้เพิ่ม triple-slash reference ไปยัง client types ของ vite-plugin-pwa ที่ด้านบนสุดของ vite-env.d.ts (หรือไฟล์ .d.ts ใดก็ได้ในโปรเจกต์):
/// <reference types="vite-plugin-pwa/client" />วิธีนี้จะบอก TypeScript เกี่ยวกับ virtual module declaration ที่ vite-plugin-pwa มาพร้อม ซึ่งจะแก้ปัญหา import error โดยไม่ต้องติดตั้ง package เพิ่มเติม
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ตัวเลือก | Benefit | Cost |
|---|---|---|
| แสดง toast/banner ให้ผู้ใช้กด “Update now” เอง | ผู้ใช้ควบคุม timing ไม่โดนตัดงานกลางคัน | ผู้ใช้ที่ไม่สนใจ toast อาจติดเวอร์ชันเก่าไปนานมาก |
| Auto-reload ทันทีที่มี update (ไม่ถาม) | ผู้ใช้อยู่กับเวอร์ชันล่าสุดตลอดเวลาโดยไม่ต้องทำอะไร | เสี่ยง reload กลางที่ผู้ใช้กำลังกรอกฟอร์มหรือทำงานค้างอยู่ |
ข้อผิดพลาดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ข้อผิดพลาดที่พบบ่อย”- ไม่ debounce/dedupe การแสดง update prompt ทำให้ toast โผล่ซ้ำหลายอันถ้า service worker event ยิงมากกว่าหนึ่งครั้ง
- ปิด prompt ทิ้งโดยไม่เก็บ state ทำให้ผู้ใช้กดปิดไปแล้วแอปไม่มีทางเตือนซ้ำอีกเลยแม้จะยังใช้เวอร์ชันเก่าอยู่
- ไม่ผูกปุ่ม “Update” เข้ากับ
updateServiceWorker()ของ plugin จริงๆ ทำให้กดแล้วไม่มีอะไรเกิดขึ้น
💡 ตัวอย่างจากของจริง
Vite PWA ecosystem (Elk) — ใช้ virtual:pwa-register hook แสดง toast “New content available, click to reload” เป็น pattern มาตรฐาน
Google Docs — แสดง banner แจ้งเตือนอัปเดตให้ผู้ใช้กดเองแทนที่จะ force reload ระหว่างที่กำลังแก้เอกสารอยู่