Skip to content

Commit 741fcff

Browse files
committed
fix: toast
1 parent 5657a52 commit 741fcff

5 files changed

Lines changed: 72 additions & 22 deletions

File tree

app/controllers/inertia_example_controller.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,12 @@ def create
2525
flash[:notice] = "POST request successful! Toast triggered from backend."
2626
redirect_to controller: "inertia_example", action: "demo"
2727
end
28+
29+
def client_fetch
30+
if rand < 0.5
31+
render json: { message: "Request succeeded!" }, status: :ok
32+
else
33+
render json: { message: "Request failed (simulated error)." }, status: :unprocessable_entity
34+
end
35+
end
2836
end

app/frontend/components/Layout.tsx

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,11 @@
1-
import { usePage } from '@inertiajs/react'
21
import type { ReactNode } from 'react'
3-
import { useEffect } from 'react'
4-
import { toast } from 'sonner'
52
import { Toaster } from '@/components/ui/sonner'
63

74
interface LayoutProps {
85
children: ReactNode
96
}
107

118
export default function Layout({ children }: LayoutProps) {
12-
const { flash } = usePage().props as { flash?: { notice?: string; alert?: string } }
13-
14-
useEffect(() => {
15-
if (flash?.notice) toast.success(flash.notice)
16-
if (flash?.alert) toast.error(flash.alert)
17-
}, [flash?.notice, flash?.alert])
18-
199
return (
2010
<>
2111
<Toaster position="top-right" richColors closeButton />

app/frontend/entrypoints/inertia.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { createInertiaApp, router } from '@inertiajs/react'
33
import { createElement, type ReactNode } from 'react'
44
import { createRoot } from 'react-dom/client'
55
import snakecaseKeys from 'snakecase-keys'
6+
import { toast } from 'sonner'
67
import ErrorBoundary from '@/components/ErrorBoundary'
78
import Layout from '@/components/Layout'
89

@@ -53,7 +54,11 @@ createInertiaApp({
5354

5455
setup({ el, App, props }) {
5556
if (el) {
56-
createRoot(el).render(createElement(ErrorBoundary, null, createElement(App, props)))
57+
if (!(el as any).__reactRoot) {
58+
;(el as any).__reactRoot = createRoot(el)
59+
}
60+
const root = (el as any).__reactRoot
61+
root.render(createElement(ErrorBoundary, null, createElement(App, props)))
5762
} else {
5863
console.error(
5964
'Missing root element.\n\n' +
@@ -89,3 +94,11 @@ const redirectToErrorPage = (event: Event) => {
8994

9095
document.addEventListener('inertia:exception', redirectToErrorPage)
9196
document.addEventListener('inertia:invalid', redirectToErrorPage)
97+
98+
// HMR-safe: clean up previous listener before re-registering
99+
;(window as any).__flashToastCleanup?.()
100+
;(window as any).__flashToastCleanup = router.on('flash', (event) => {
101+
const flash = event.detail.flash as { notice?: string; alert?: string }
102+
if (flash?.notice) toast.success(flash.notice)
103+
if (flash?.alert) toast.error(flash.alert)
104+
})

app/frontend/pages/inertia_example/demo.tsx

Lines changed: 49 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { router } from '@inertiajs/react'
22
import { useEffect, useState } from 'react'
3+
import { toast } from 'sonner'
34
import {
45
Accordion,
56
AccordionContent,
@@ -124,6 +125,7 @@ function ComponentShowcase({
124125
}
125126

126127
const Demo = () => {
128+
const [fetching, setFetching] = useState(false)
127129
const [sliderValue, setSliderValue] = useState([50])
128130
const [switchChecked, setSwitchChecked] = useState(false)
129131
const [selectValue, setSelectValue] = useState('')
@@ -134,6 +136,28 @@ const Demo = () => {
134136
const [dialogOpen, setDialogOpen] = useState(false)
135137
const [alertDialogOpen, setAlertDialogOpen] = useState(false)
136138

139+
const handleClientFetch = async () => {
140+
setFetching(true)
141+
try {
142+
const csrfToken =
143+
document.querySelector<HTMLMetaElement>('meta[name="csrf-token"]')?.content ?? ''
144+
const res = await fetch('/demo/fetch', {
145+
method: 'POST',
146+
headers: { 'X-CSRF-Token': csrfToken, 'Content-Type': 'application/json' },
147+
})
148+
const data = await res.json()
149+
if (res.ok) {
150+
toast.success(data.message)
151+
} else {
152+
toast.error(data.message)
153+
}
154+
} catch {
155+
toast.error('Network error')
156+
} finally {
157+
setFetching(false)
158+
}
159+
}
160+
137161
useEffect(() => {
138162
if (switchChecked) {
139163
document.documentElement.classList.add('dark')
@@ -153,17 +177,31 @@ const Demo = () => {
153177
</div>
154178
</div>
155179

156-
<Card>
157-
<CardHeader>
158-
<CardTitle className="text-base">Flash Messages Test</CardTitle>
159-
<CardDescription>Click buttons to trigger flash messages via Inertia</CardDescription>
160-
</CardHeader>
161-
<CardContent className="flex flex-wrap gap-2">
162-
<Button variant="default" onClick={() => router.post('/demo')}>
163-
Trigger POST Toast
164-
</Button>
165-
</CardContent>
166-
</Card>
180+
<div className="grid gap-4 sm:grid-cols-2">
181+
<Card>
182+
<CardHeader>
183+
<CardTitle className="text-base">Flash Messages Test</CardTitle>
184+
<CardDescription>Click buttons to trigger flash messages via Inertia</CardDescription>
185+
</CardHeader>
186+
<CardContent className="flex flex-wrap gap-2">
187+
<Button variant="default" onClick={() => router.post('/demo')}>
188+
Trigger POST Toast
189+
</Button>
190+
</CardContent>
191+
</Card>
192+
193+
<Card>
194+
<CardHeader>
195+
<CardTitle className="text-base">Client Fetch Demo</CardTitle>
196+
<CardDescription>Uses raw fetch() — 50% success, 50% error</CardDescription>
197+
</CardHeader>
198+
<CardContent className="flex flex-wrap gap-2">
199+
<Button variant="outline" onClick={handleClientFetch} disabled={fetching}>
200+
{fetching ? 'Sending…' : 'Send Fetch Request'}
201+
</Button>
202+
</CardContent>
203+
</Card>
204+
</div>
167205

168206
<Tabs value={tabsValue} onValueChange={setTabsValue}>
169207
<TabsList>

config/routes/demo.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
get "inertia-example", to: "inertia_example#index"
55
get "demo", to: "inertia_example#demo"
66
post "demo", to: "inertia_example#create"
7+
post "demo/fetch", to: "inertia_example#client_fetch"

0 commit comments

Comments
 (0)