Skip to content

Commit 42bcbd8

Browse files
authored
Merge pull request #40 from forge-42/passthrough_toasts
Passthrough toasts
2 parents 9963426 + 2cf4461 commit 42bcbd8

5 files changed

Lines changed: 61 additions & 26 deletions

File tree

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "remix-toast",
3-
"version": "3.0.0",
3+
"version": "3.1.0",
44
"description": "Utility functions for server-side toast notifications",
55
"type": "module",
66
"main": "./dist/index.cjs",
@@ -100,4 +100,4 @@
100100
"vite": "^4.2.1",
101101
"vitest": "^0.30.1"
102102
}
103-
}
103+
}

src/index.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
1+
import { type SessionStorage, createCookieSessionStorage, data as dataFn, redirect } from "react-router";
12
import {
2-
SessionIdStorageStrategy,
3-
type SessionStorage,
4-
createCookieSessionStorage,
5-
data as dataFn,
6-
redirect,
7-
} from "react-router";
8-
import { type FlashSessionValues, type ToastMessage, flashSessionValuesSchema } from "./schema";
3+
FLASH_SESSION,
4+
type FlashSessionValues,
5+
type ToastMessage,
6+
type ToastMessageWithoutType,
7+
flashSessionValuesSchema,
8+
} from "./schema";
99
import { type ToastCookieOptions, sessionStorage, toastCookieOptions } from "./session";
1010

11-
const FLASH_SESSION = "flash";
12-
1311
/**
1412
* Sets the cookie options to be used for the toast cookie
1513
*
@@ -71,7 +69,7 @@ type BaseFactoryType = {
7169
const dataWithToastFactory = ({ type, session }: BaseFactoryType) => {
7270
return <T>(
7371
data: T,
74-
messageOrToast: string | Omit<ToastMessage, "type">,
72+
messageOrToast: string | ToastMessageWithoutType,
7573
init?: ResponseInit,
7674
customSession?: SessionStorage,
7775
) => {
@@ -83,7 +81,7 @@ const dataWithToastFactory = ({ type, session }: BaseFactoryType) => {
8381
const redirectWithToastFactory = ({ type, session }: BaseFactoryType) => {
8482
return (
8583
redirectUrl: string,
86-
messageOrToast: string | Omit<ToastMessage, "type">,
84+
messageOrToast: string | ToastMessageWithoutType,
8785
init?: ResponseInit,
8886
customSession?: SessionStorage,
8987
) => {

src/middleware/index.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,39 @@ import {
55
unstable_createContext,
66
} from "react-router";
77
import { type ToastMessage, getToast as getToastPrimitive } from "..";
8+
import { FLASH_SESSION } from "../schema";
9+
import { sessionStorage } from "../session";
810

911
const toastContext = unstable_createContext<ToastMessage | null>(null);
12+
const sessionToastContext = unstable_createContext<ToastMessage | null>(null);
1013

1114
export function unstable_toastMiddleware(props?: { customSession?: SessionStorage }): unstable_MiddlewareFunction {
1215
const { customSession } = props || {};
13-
16+
const sessionToUse = customSession || sessionStorage;
1417
return async function toastMiddleware({ request, context }, next) {
1518
const { toast, headers } = await getToastPrimitive(request, customSession);
19+
1620
context.set(toastContext, toast ?? null);
21+
// Call the next middleware or route handler
1722
const res = await next();
23+
1824
if (res instanceof Response && toast) {
19-
res.headers.append("Set-Cookie", headers.get("Set-Cookie") || "");
25+
res.headers.append("Set-Cookie", headers.get("Set-Cookie") ?? "");
2026
}
27+
const toastToSet = context.get(sessionToastContext);
28+
29+
if (res instanceof Response && toastToSet) {
30+
const session = await sessionToUse.getSession(request.headers.get("Cookie"));
31+
session.flash(FLASH_SESSION, { toast: toastToSet });
32+
res.headers.append("Set-Cookie", await sessionToUse.commitSession(session));
33+
}
34+
2135
return res;
2236
};
2337
}
2438

39+
export const setToast = (context: unstable_RouterContextProvider, toast: ToastMessage | null) => {
40+
context.set(sessionToastContext, toast);
41+
};
42+
2543
export const getToast = (context: unstable_RouterContextProvider) => context.get(toastContext);

src/schema.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,27 @@
11
import { z } from "zod";
22

3-
export const toastMessageSchema = z.object({
4-
message: z.string(),
5-
description: z.string().optional(),
6-
duration: z.number().int().positive().optional(),
7-
type: z.custom<"info" | "success" | "error" | "warning">(),
8-
});
3+
export const toastMessageSchema = z
4+
.object({
5+
message: z.string(),
6+
description: z.string().optional(),
7+
duration: z.number().int().positive().optional(),
8+
type: z.custom<"info" | "success" | "error" | "warning">(),
9+
})
10+
.passthrough();
11+
12+
const toastMessageWithoutTypeSchema = z
13+
.object({
14+
message: z.string(),
15+
description: z.string().optional(),
16+
duration: z.number().int().positive().optional(),
17+
})
18+
.passthrough();
919

1020
export const flashSessionValuesSchema = z.object({
1121
toast: toastMessageSchema.optional(),
1222
});
1323

14-
export type ToastMessage = z.infer<typeof toastMessageSchema>;
15-
24+
export type ToastMessage = z.infer<typeof toastMessageSchema> & {};
25+
export type ToastMessageWithoutType = z.infer<typeof toastMessageWithoutTypeSchema> & {};
1626
export type FlashSessionValues = z.infer<typeof flashSessionValuesSchema>;
27+
export const FLASH_SESSION = "flash";

test-apps/react-router/app/routes/_index.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
11
import type { MetaFunction } from "react-router";
22
import { Link, useSubmit } from "react-router";
3-
import { redirectWithError } from "remix-toast";
3+
import { setToast } from "remix-toast/middleware";
4+
import type { Route } from "./+types/_index";
45

56
export const meta: MetaFunction = () => {
67
return [{ title: "New Remix App" }, { name: "description", content: "Welcome to Remix!" }];
78
};
8-
export const action = () => {
9-
return redirectWithError("/test", "This is an error message");
9+
export const action = ({ context }: Route.ActionArgs) => {
10+
setToast(context, {
11+
message: "test toast",
12+
description: "from middleware",
13+
type: "success",
14+
});
15+
console.log("here?");
16+
return null;
1017
};
1118

1219
export default function Index() {
@@ -15,6 +22,7 @@ export default function Index() {
1522
<div style={{ fontFamily: "system-ui, sans-serif", lineHeight: "1.8" }}>
1623
<h1>Welcome to Remix</h1>
1724
<ul>
25+
{/* biome-ignore lint/a11y/useButtonType: <explanation> */}
1826
<button onClick={() => submit("/", { method: "post" })}>Click Me</button>
1927
<li>
2028
<a target="_blank" href="https://remix.run/tutorials/blog" rel="noreferrer">

0 commit comments

Comments
 (0)