Modal + banner UI
React components shipping in scaffold.
The React side: a banner that appears when an update is available, a modal that shows progress + the "Install" button. The scaffold ships these β this lesson explains the components so you can customise them.
The hook that polls
import { useQuery } from '@tanstack/react-query'import { CheckForUpdates } from '../../wailsjs/go/main/Updater'export function useUpdateChecker() {const q = useQuery({queryKey: ['updater', 'latest'],queryFn: () => CheckForUpdates(),refetchInterval: 6 * 60 * 60 * 1000, // 6hrefetchOnWindowFocus: false,})const isUpdateAvailable = !!q.data?.is_newerreturn { update: q.data, isUpdateAvailable, checkNow: () => q.refetch() }}
Cheap call β one HTTP round-trip to GitHub. Defaulting to 6 hours means each running app costs ~4 GitHub requests/day.
The banner
export function UpdateBanner() {const { update, isUpdateAvailable } = useUpdateChecker()const [dismissed, setDismissed] = useState(false)const [modalOpen, setModalOpen] = useState(false)if (!isUpdateAvailable || dismissed) return nullreturn (<><div className="flex items-center gap-3 px-4 py-2 border-b bg-primary/5"><Download className="h-3.5 w-3.5 text-primary" /><span className="flex-1 text-sm">Update available: <strong>v{update.version}</strong></span><button onClick={() => setModalOpen(true)} className="text-xs text-primary">Update now</button><button onClick={() => setDismissed(true)} className="text-muted-foreground"><X className="h-3.5 w-3.5" /></button></div>{modalOpen && <UpdateModal update={update} onClose={() => setModalOpen(false)} />}</>)}
Mount once in the root layout. Dismissal is per-session β the banner reappears on next launch if the update is still pending.
The modal β download progress + install
export function UpdateModal({ update, onClose }) {const [progress, setProgress] = useState({ status: 'idle', bytesDownloaded: 0, bytesTotal: 0 })// Auto-start the download on mountuseEffect(() => {StartDownload(update.download_url, update.version)}, [])// Poll progress every 500msuseEffect(() => {const id = setInterval(async () => {const p = await GetProgress()setProgress(p)}, 500)return () => clearInterval(id)}, [])const pct = (progress.bytesDownloaded / progress.bytesTotal) * 100return (<Dialog open onOpenChange={onClose}><DialogContent><h2>Update available</h2><p className="text-sm text-muted-foreground">v{APP_VERSION} β v{update.version}</p>{/* Release notes β capped so the install button stays visible */}<div className="max-h-[180px] overflow-y-auto text-sm">{update.release_notes}</div>{progress.status === 'downloading' && (<><div className="flex justify-between text-xs"><span>Downloadingβ¦</span><span>{Math.round(pct)}%</span></div><div className="h-2 bg-muted rounded"><div className="h-full bg-primary rounded" style={{ width: pct + '%' }} /></div></>)}{progress.status === 'downloaded' && (<Button onClick={InstallAndRestart}>Install & restart</Button>)}</DialogContent></Dialog>)}
Error UX
Download fails (network blip, GitHub down). The user shouldn't be stuck. The scaffold handles four states:
idleβ nothing happeningdownloadingβ progress bardownloadedβ "Install & restart" buttonerrorβ error message + "Try again" button
Where to mount the banner
Top of App.tsx, above the routes. Always visible when an update is pending β but stays out of the way (one row).
<div className="h-screen flex flex-col"><TitleBar /><UpdateBanner /> {/* β here */}<main className="flex-1 overflow-auto">{/* routes */}</main></div>
Quick check
Try it
Inspect the three scaffolded components:
src/components/update-banner.tsxsrc/components/update-modal.tsxsrc/hooks/use-update-checker.ts
In notes.md, write down:
- The polling interval the hook uses
- The four progress states the modal handles
- Where the dismissal state is stored (component / global)
What's next
Last lesson of the chapter β the release script that builds, tags, and publishes a new version to GitHub so the updater can find it.
Spot a typo? Have an idea?
Help us improve this lesson. One click opens a GitHub issue with the lesson URL pre-filled β suggest clearer wording, report a bug, or request more depth. The course keeps improving thanks to learners like you.
Suggest an improvement on GitHub