Effortlessly Add Confetti to Redirects in Remix: Step-by-Step

Yesterday I wrote about how we can use flash session to show toast messages to the user in this article:

https://alemtuzlak.hashnode.dev/handling-toasts-in-remix

Well, today I want to expand on this and add a fun little utility that will brighten up the day for even the grouchiest of users! Confetti!!

Small reminder, the code examples expand upon the code from the original article and adds diffs to show what has changed on top of the toast notification implementation so if something is not clear check the first article and report back!

If you haven't already figured it out, and you probably have, the purpose of this article is to show you how to use the flash session to rain down confetti on the user's screen when you redirect him. I love to use this UI trick to spice up my websites and it's really easy to do to boot, so why not?!

Extending the server utilities

So, as we already know Remix has a darker server side, and a lighter client side! We first start with the server side and explain how we pull this off easily!

First off, let's extend our validation to support confetti as well:

// Schema to validate the flash session storage
const flashSessionValuesSchema = z.object({ 
  // validation schema from above
+ confetti: z.string().optional(),
  toast: toastMessageSchema.optional()
});

Now we will also validate the confetti if it's present in our flash session! Great!

So this part is completely optional, I use crypto to generate random UUIDs for confetti, but you can use any approach or package that suits your project, I usually use crypto for other things in Remix so this was the easiest for me but any unique random string will work!

npm install crypto

Alright, so next up we add the redirect utility like we did for toasts:

// Optional import, you can use Math.random() or something like this
import { randomUUID } from "crypto";

export function redirectWithConfetti(url: string, init?: ResponseInit) {
  return redirectWithFlash(url, { 
    // This is the part where we generate a random unique string, it can
    // also be something like Math.random().toString(), up to you!
    confetti: randomUUID() 
  }, init);
}

Okay, you've done that, what's next? Nothing!

This is all it takes on the server side! Now onto the client!

Client-side implementation

So for this part, I recommend using the "confetti-react" package, but it's up to you, you can use a different one! You will also need "remix-utils" for this one, or you can copy-paste their ClientOnly component implementation. I will be using the above-mentioned packages, so let's install them:

npm install confetti-react remix-utils

And now you need to create a confetti component, you will do this in your components folder wherever that might be like so:

import { Index as ConfettiShower } from "confetti-react";
import { ClientOnly } from "remix-utils";

/**
 * confetti is a unique random identifier which re-renders the component
 */
export function Confetti({ confetti }: { confetti?: string }) {
  // If confetti isn't passed in we don't render the component
  if (!confetti) {
    return null;
  }
  return (
    // Needs to be client-only
    <ClientOnly>
      {() => (
        <ConfettiShower
          // This is the unique identifier passed by the backend
          // We use it as a key so the component is not re-rendered if the root.tsx
          // re-renders it so the confetti only rains down once on inital redirect
          key={confetti}
          // Tells the component to execute the confetti
          run={Boolean(confetti)}
          // Rest of these are up to you!
          recycle={false}
          numberOfPieces={500}
          width={window.innerWidth}
          height={window.innerHeight}
        />
      )}
    </ClientOnly>
  );
}

And now we import this into our root.tsx file:

import { toast, ToastContainer } from "react-toastify";
+ import { Confetti } from "your-path-here";

export const loader = async ({ request }: LoaderArgs) => {
  // Your root loader code
  const { flash, headers } = await getFlashSession(request);
  return json({ /** your data here */, flash }, { headers });
}

export default function App() {
    const { /** your stuff */, flash } = useLoaderData<typeof loader>();
    // This will show your toast every time you redirect the user
    useEffect(() => {
      if (flash.toast) {
        toast(flash.toast.type, flash.toast.text);
      }
    }, [flash.toast]);

    return (
      <html>
        <body>
           <!-- your stuff -->
+          <Confetti confetti={flash.confetti} />
           <ToastContainer theme="colored" position="bottom-right" />
        </body>
      </html>
    );
}

After this I do the following in the actions I want to trigger confetti:

export const action = () => {
  // do some stuff
  return redirectWithConfetti("/dashboard");
}

And that's pretty much it! You have full confetti raining down! Oh, did I mention these two approaches (toasts & confetti) were added to Kent's epic stack? Check out the demo of it down below:

How it works?

Alright, this is awesome! But how the heck does this work? Well let's go over it, so I already explained how the flash session works with the toast notifications. Well, this uses the same approach, we store the unique string ID into the flash session, redirect the user, consume the flash session and send it through our loader to the client!

So the interesting part is how it works on the client! So the reason we generate a random string ID is so that we can pass it as a "key" prop to the Confetti component, If you know how React works you will know it uses keys to know when something needs to be re-rendered. So whenever we run the confetti component via the unique key we guarantee it will rain confetti only once, even if the root.tsx file is re-rendered as long as it has the same value it will not re-trigger the confetti.

And if you redirect the user afterwards the flash session is gone so it won't render the component at all and there won't be any confetti!

Thank you!

If you've reached the end you're a champ! I hope you liked my article.

If you wish to support me follow me on Twitter here:

https://twitter.com/AlemTuzlak59192

or if you want to follow my work you can do so on GitHub:

https://github.com/AlemTuzlak

And you can also sign up for the newsletter to get notified whenever I publish something new!