UNPKG

sveltekit-flash-message

Version:

Send temporary data to the next request after redirect. Works with both SSR and client.

382 lines (265 loc) 13.1 kB
# sveltekit-flash-message This [Sveltekit](https://svelte.dev/docs/kit/) library passes temporary data to the next request, usually in [form actions](https://kit.svelte.dev/docs/form-actions) and [endpoints](https://kit.svelte.dev/docs/routing#server). It's useful for displaying a success or failure message after a POST, which should not always be displayed at the form, rather as a message on the page that the request was redirected to. Since it's a temporary message it's also known as a "flash message", especially known from PHP apps, since it's easy to add this functionality with PHP's built-in session handling. With SvelteKit it's a bit harder, but this library was made to alleviate that, encouraging well-behaved web apps that [Redirects after Post](https://www.theserverside.com/news/1365146/Redirect-After-Post). ## Installation ``` pnpm i -D sveltekit-flash-message ``` ``` npm i -D sveltekit-flash-message ``` ## How to use ### 1. Add the flash message to app.d.ts (Typescript only) In `src/app.d.ts`, add the type for the flash message to `App.PageData` as an optional property called `flash`. It can be as simple as a `string`, or something more advanced. It has to be serializable though, so only JSON-friendly data structures. For example: **src/app.d.ts** ```ts // See https://svelte.dev/docs/kit/types#app.d.ts // for information about these interfaces declare global { namespace App { // interface Error {} // interface Locals {} interface PageData { flash?: { type: 'success' | 'error'; message: string }; } // interface PageState {} // interface Platform {} } } export {}; ``` ### 2. Wrap the load function of a top-level +layout or +page route If you're not using any [load functions](https://kit.svelte.dev/docs/load), this is a simple step. Create a `src/routes/+layout.server.ts` file with the following content: **src/routes/+layout.server.ts** ```ts export { load } from 'sveltekit-flash-message/server'; ``` But most likely you already have a top-level `load` function, in which case you can import `loadFlash` and wrap your load function with it: **src/routes/+layout.server.ts** ```ts import { loadFlash } from 'sveltekit-flash-message/server'; export const load = loadFlash(async (event) => { const data = { someOther: 'data' }; return data; }); ``` **Note:** There cannot be any additional `loadFlash` calls in routes below, as the flash cookie is deleted when it is found the first time. ### 3. Display the flash message Import `getFlash` in a layout or page component to display the flash message. `getFlash` will return a store that you'll use to access the message: **src/routes/+layout.svelte** ```svelte <script lang="ts"> import { getFlash } from 'sveltekit-flash-message'; import { page } from '$app/state'; const flash = getFlash(page); </script> {#if $flash} {@const bg = $flash.type == 'success' ? '#3D9970' : '#FF4136'} <div style:background-color={bg} class="flash">{$flash.message}</div> {/if} ``` ## 4. Send flash messages ### Server-side To send a flash message from the server, import `redirect` from `sveltekit-flash-message/server` and use it in [load functions](https://kit.svelte.dev/docs/load#redirects) and [form actions](https://kit.svelte.dev/docs/form-actions#anatomy-of-an-action-redirects). **Note:** With SvelteKit 2, you don't need to [throw the redirect](https://kit.svelte.dev/docs/migrating-to-sveltekit-2#redirect-and-error-are-no-longer-thrown-by-you), just call `redirect`. If you're still on SvelteKit 1, `throw` the function call. ```ts import { redirect } from 'sveltekit-flash-message/server' // The most specific: Redirect with a specific HTTP status to a specific location. redirect( status: number, location: string, message: App.PageData['flash'], event: RequestEvent | Cookies ) // Makes a 303 redirect to a specific location. redirect( location: string, message: App.PageData['flash'], event: RequestEvent | Cookies ) // Makes a 303 redirect to the current location. redirect( message: App.PageData['flash'], event: RequestEvent ) // For compatibility, the sveltekit signature can also be used, which will send no flash message. redirect( status: number, location: string, ) ``` #### Form action example **src/routes/todos/+page.server.ts** ```ts import { redirect } from 'sveltekit-flash-message/server'; export const actions = { default: async ({ request, locals, cookies }) => { const form = await request.formData(); await api('POST', `/todos/${locals.userid}`, { text: form.get('text') }); redirect('/', { type: 'success', message: "That's the entrepreneur spirit!" }, cookies); } }; ``` #### Endpoint example **src/routes/todos/+server.ts** ```ts import type { RequestEvent } from '@sveltejs/kit'; import { redirect } from 'sveltekit-flash-message/server'; export const POST = async ({ cookies }) => { redirect('/', { type: 'success', message: 'Endpoint POST successful!' }, cookies); }; ``` #### Setting without redirecting If you want to display a flash message without redirecting, as an error message when validation fails for example, you can use the `setFlash` function: ```ts import { fail } from '@sveltejs/kit'; import { setFlash } from 'sveltekit-flash-message/server'; export const actions = { default: async ({ request, cookies }) => { const form = await request.formData(); if (!form.get('text')) { setFlash({ type: 'error', message: 'Please enter text.' }, cookies); return fail(400); } } }; ``` ### Client-side If you want to update the flash message on the client, use `getFlash` in any component: **src/routes/some-route/+page.svelte** ```svelte <script> import { getFlash } from 'sveltekit-flash-message'; import { page } from '$app/state'; const flash = getFlash(page); function showMessage() { $flash = { type: 'success', message: 'Updated from other component!' }; } </script> <button on:click={showMessage}>Show flash message</button> ``` This will of course not set a cookie for the next request, it'll only update the flash message on the client. ## Client-side fetching and redirecting The flash message will update automatically on redirect or navigation, but when using [fetch](https://kit.svelte.dev/docs/web-standards#fetch-apis), you must call `updateFlash` afterwards: ```svelte <script lang="ts"> import { updateFlash } from 'sveltekit-flash-message'; import { page } from '$app/state'; async function submitForm(e: Event) { const form = e.target as HTMLFormElement; const body = new FormData(e.target as HTMLFormElement); await fetch(form.action, { method: 'POST', body }); await updateFlash(page); } </script> <form method="POST" action="/test" on:submit|preventDefault={submitForm}> <input type="text" name="test" value="TEST" /> <button>Submit with fetch</button> </form> ``` `updateFlash` can take a second parameter, which is used to run a function **before** updating, so navigation events will pass through before showing the flash message. This is useful when you want to redirect based on the fetch response: ```ts async function submitForm(e: Event) { const response = await fetch(new URL('/logout', $page.url), { method: 'POST' }); if (response.redirected) { await updateFlash(page, () => goto(response.url, { invalidateAll: true })); } } ``` ## Toast messages, event-style A common use case for flash messages is to show a toast notification, but a toast is more like an event than data that should be displayed on the page, as we've done previously. But you can use the `flash` store as an event handler with a reactive statement: **src/routes/+layout.svelte** ```ts import { getFlash } from 'sveltekit-flash-message'; import { page } from '$app/state'; import toast, { Toaster } from 'svelte-french-toast'; const flash = getFlash(page); $: if ($flash) { toast($flash.message, { icon: $flash.type == 'success' ? '' : '' }); // Clear the flash message to avoid double-toasting. $flash = undefined; } ``` ## Flash message options When calling `getFlash`, you can specify options, which will be inherited for the current route and the ones below. ```ts const flash = getFlash(page, { clearOnNavigate: true, clearAfterMs: undefined, clearArray: false, flashCookieOptions: CookieSerializeOptions }); ``` You can also use `initFlash`, if you don't display a flash message in a certain layout but still want to set options for the routes below: ```ts import { initFlash } from 'sveltekit-flash-message'; import { page } from '$app/state'; initFlash(page, { clearAfterMs: 10000 }); ``` ### clearOnNavigate If `true` (the default), the flash message will be removed when navigating to a different route. ### clearAfterMs Can be set to a number of milliseconds before the flash message is automatically set to `undefined`. ### clearArray If you specify `App.PageData['flash']` as an array, the library will concatenate messages into the array instead of replacing them. But if you always want to clear the previous messages for arrays, set the `clearArray` option to `true`. If your flash message isn't an array, this option will have no effect. ### flashCookieOptions You can change the options for the cookie being sent, like this on the server: ```ts import { loadFlash, flashCookieOptions } from 'sveltekit-flash-message/server'; flashCookieOptions.sameSite = 'lax'; export const load = loadFlash(async (event) => { // ...load function... }); ``` And correspondingly, on the client (in a top-level component): ```ts import { initFlash } from 'sveltekit-flash-message'; initFlash(page, { flashCookieOptions: { sameSite: 'lax' } }); ``` All options can be found in the [cookie npm package](https://github.com/jshttp/cookie#options-1). Default options for the flash cookie are: ```ts { path: '/', maxAge: 120, sameSite: 'strict', httpOnly: false // Setting this to true will most likely break things client-side. } ``` The name of the cookie, `flash`, cannot be changed currently, let me know if that's inconvenient. ## Securing the flash message Since the flash message is transferred in a cookie, it can be easily tampered with, so don't trust its content. Treat it like you do with any user data - hanging from a ten-foot pole over a fiery pit. 🔥 So never use `{@html}` to display it, and if you need to persist it for some reason, make sure you validate it. ## Together with Superforms The sister library to sveltekit-flash-message is [Superforms](https://superforms.rocks), the all-in-one solution for forms in SvelteKit. You can use them together without any extra work, but there are options for closer integration, [found here](https://superforms.rocks/flash-messages) on the Superforms website. # Notes ## When setting cookies in a response If you're using `+hooks.server.ts/js`, or anywhere you have access to `response`, calling `response.headers.set('set-cookie', ...)` will discard the flash message cookie. You must use `response.headers.append` instead. ## Redirecting in the load function In SvelteKit, links are [preloaded on hover](https://kit.svelte.dev/docs/link-options#data-sveltekit-preload-data) for increased responsiveness of the app. This can have the side-effect of accidentally setting a flash cookie, if a flash message redirect is made in a load function, and the user hovers over a link leading to it, so it is preloaded. To prevent this, set the `data-sveltekit-preload-data="tap"` attribute on links where a redirect could happen in the load function. # Migration guides ## From 0.x to 1.x The only thing you need to do when upgrading to 1.x is to remove all calls to `updateFlash` in `use:enhance`. ```diff <form method="POST" - use:enhance={() => - ({ update }) => - updateFlash(page, update)} + use:enhance > ``` ## From 1.x to 2.x 1. Rename functions: - `loadFlashMessage` is deprecated and renamed to `loadFlash`. - `initFlash` is only needed for configuration, `getFlash` can be used directly in most cases. 2. If you've added the `beforeNavigate` snippet that clears the flash message after navigation, it's now automatic and can be removed. (It can be prevented by setting the `clearOnNavigate` option to `false`.) 3. If you're using the snippet for clearing the message after a certain amount of time, you can remove it and use the `clearAfterMs` option instead. ## Feedback and issues Please [open a github issue](https://github.com/ciscoheat/sveltekit-flash-message/issues) for suggestions, if you find a bug or have feedback in general!