Skip to content

Commit 23ef1c6

Browse files
committed
add toast/flash docs
1 parent 860d42d commit 23ef1c6

File tree

4 files changed

+86
-13
lines changed

4 files changed

+86
-13
lines changed

app/root.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ export async function loader({ request }: DataFunctionArgs) {
118118
},
119119
{
120120
headers: combineHeaders(
121-
new Headers({ 'Server-Timing': timings.toString() }),
121+
{ 'Server-Timing': timings.toString() },
122122
flasHeaders,
123123
),
124124
},

app/utils/flash-session.server.ts

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ function getSessionFromRequest(request: Request) {
3535

3636
return sessionStorage.getSession(cookie)
3737
}
38+
39+
/**
40+
* Helper method used to add flash session values to the session
41+
*/
42+
export async function flashMessage(
43+
flash: FlashSessionValues,
44+
headers?: ResponseInit['headers'],
45+
) {
46+
const session = await sessionStorage.getSession()
47+
session.flash(FLASH_SESSION, flash)
48+
const cookie = await sessionStorage.commitSession(session)
49+
const newHeaders = new Headers(headers)
50+
newHeaders.append('Set-Cookie', cookie)
51+
return newHeaders
52+
}
53+
3854
/**
3955
* Helper method used to redirect the user to a new page with flash session values
4056
* @param url Url to redirect to
@@ -47,16 +63,9 @@ export async function redirectWithFlash(
4763
flash: FlashSessionValues,
4864
init?: ResponseInit,
4965
) {
50-
const session = await sessionStorage.getSession()
51-
session.flash(FLASH_SESSION, flash)
52-
const headers = new Headers(init?.headers)
53-
const flashCookie = await sessionStorage.commitSession(session)
54-
// We append the flash cookie to the session
55-
headers.append('Set-Cookie', flashCookie)
56-
5766
return redirect(url, {
5867
...init,
59-
headers,
68+
headers: await flashMessage(flash, init?.headers),
6069
})
6170
}
6271
/**

app/utils/misc.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,10 @@ export function getDomainUrl(request: Request) {
3636
/**
3737
* Merge multiple headers objects into one (uses set so headers are overridden)
3838
*/
39-
export function mergeHeaders(...headers: Array<Headers>) {
39+
export function mergeHeaders(...headers: Array<ResponseInit['headers']>) {
4040
const merged = new Headers()
4141
for (const header of headers) {
42-
for (const [key, value] of header.entries()) {
42+
for (const [key, value] of new Headers(header).entries()) {
4343
merged.set(key, value)
4444
}
4545
}
@@ -49,10 +49,10 @@ export function mergeHeaders(...headers: Array<Headers>) {
4949
/**
5050
* Combine multiple header objects into one (uses append so headers are not overridden)
5151
*/
52-
export function combineHeaders(...headers: Array<Headers>) {
52+
export function combineHeaders(...headers: Array<ResponseInit['headers']>) {
5353
const combined = new Headers()
5454
for (const header of headers) {
55-
for (const [key, value] of header.entries()) {
55+
for (const [key, value] of new Headers(header).entries()) {
5656
combined.append(key, value)
5757
}
5858
}

docs/toasts.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Toasts
2+
3+
Toast messages are great ways to temporarily call someone's attention to
4+
something. They are often used to notify users of a successful or failed action.
5+
6+
![confetti](https://github.com/epicweb-dev/epic-stack/assets/1500684/6183b362-5682-4ab0-aa1a-7cc1e4f72f9e)
7+
8+
![toasts](https://github.com/epicweb-dev/epic-stack/assets/1500684/715d754a-9e9f-4b61-814f-881121f2fa48)
9+
10+
There are utilities in the Epic Stack for toast notifications. Additionally, the
11+
same code used to power a toast notification is also used to power the Confetti
12+
component which is used to celebrate when a user signs up for an account (feel
13+
free to remove that or use it for other things as well).
14+
15+
This is managed by a special session using a concept called "flash data" which
16+
is a temporary session value that is only available for the next request. This
17+
is a great way to pass data to the next request without having to worry about
18+
the data persisting in the session. And you don't have to worry about managing
19+
state either. It all just lives in the cookie.
20+
21+
There are two utilities you'll use for redirecting with toast/confetti
22+
notifications from the `app/utils/flash-session.server.ts` file:
23+
`redirectWithToast` and `redirectWithConfetti`. Here's a simple example of using
24+
these:
25+
26+
```tsx
27+
return redirectWithToast(`/users/${note.owner.username}/notes/${note.id}`, {
28+
title: id ? 'Note updated' : 'Note created',
29+
})
30+
// or
31+
return redirectWithConfetti(safeRedirect(redirectTo, '/'))
32+
```
33+
34+
Each of these accepts an additional argument for other `ResponseInit` options so
35+
you can set other headers, etc.
36+
37+
If you don't wish to redirect, you could use the underlying `flashMessage`
38+
directly:
39+
40+
```tsx
41+
return json(
42+
{ success: true },
43+
{
44+
headers: await flashMessage({
45+
toast: {
46+
title: 'Note updated',
47+
type: 'success',
48+
},
49+
}),
50+
},
51+
)
52+
```
53+
54+
And if you need to set multiple headers, you can pass them as a second argument
55+
to `flashMessage`:
56+
57+
```tsx
58+
flashMessage(
59+
{
60+
/* ... */
61+
},
62+
{ 'X-My-Header': 'My value' },
63+
)
64+
```

0 commit comments

Comments
 (0)