Recently, I was working on a React Router v7 app where I needed to implement a cookie banner. The app has many routes, and the requirement was to display the cookie banner on every single one of them, without exception. As all the routes share the same layout, it felt natural to add the cookie banner there. In this article, Iâll walk you through how to achieve this.
Step 1: Setting Up the Layout Component
First, letâs create a layout component and define how it will be used in our routes. Hereâs a simple example:
// app/layouts/layout.tsx
import { Outlet } from "react-router";
export function Layout() {
return (
<>
<header>...</header>
<main>
<Outlet />
</main>
<footer>...</footer>
</>
);
}
Then, we use the layout in our routing configuration:
// app/routes.ts
import { type RouteConfig, index, layout } from "@react-router/dev/routes";
export default [
layout("layouts/layout.tsx", [index("routes/home.tsx")]),
] satisfies RouteConfig;
Now that we have our layout set up, we can move on to creating the cookie banner.
Step 2: Creating the Cookie Banner Component
Letâs start by building a simple cookie banner:
// app/components/cookie-banner.tsx
import { Form } from "react-router";
export function CookieBanner() {
return (
<div>
<p>We're using cookies</p>
<p>...</p>
<Form method="POST">
<button type="submit" name="consent" value="all">
Accept all
</button>
<button type="submit" name="consent" value="necessary">
Only necessary
</button>
</Form>
</div>
);
}
Here, we use the <Form>
component from React Router for issuing POST
requests when users interact with the cookie banner. Weâll revisit the Form
in a moment.
Step 3: Adding the Cookie Banner to the Layout
Now that the cookie banner component is ready, we can include it in the layout to make it appear across all routes:
// app/layouts/layout.tsx
import { Outlet } from "react-router";
import { CookieBanner } from "~/components/cookie-banner";
export default function Layout() {
return (
<>
<header>...</header>
<main>
<Outlet />
</main>
<footer>...</footer>
<CookieBanner />
</>
);
}
At this point, the cookie banner will render on every page of the app. Try clicking the buttons, however, and youâll encounter a âMethod Not Allowedâ error. Letâs fix that.
Step 4: Handling Form Actions
The issue stems from missing an action
attribute in the <Form>
. If you donât specify an action, the formâs request is routed to the current page, and React Router doesnât know how to handle it.
A simple solution is to point the action
to the index route (or any other route that handles the logic). Update the form as follows:
// app/components/cookie-banner.tsx
<Form method="POST" action="/?index">
Now, when the form is submitted, the request will be sent to the index routeâs action. Letâs define that action to handle the form submission:
// app/routes/home.tsx
export function action() {
// Handle user cookie preferences here
return {};
}
For simplicity, this example doesnât include the logic to store user preferences. If youâd be interested in learning more about that, let me know in the comments - this could be a topic for another article!
Step 5: Improving User Experience: Redirecting Back
Currently, when users submit the form from a route that isnât the index route, theyâll be redirected back to the index route after submission. This isnât ideal from a user experience perspective, so letâs fix it.
The goal is to redirect users back to the page where they submitted the form. To achieve this, weâll capture the current pageâs pathname using the useLocation
hook and send it as part of the form data. Update the cookie banner component like so:
// app/components/cookie-banner.tsx
import { Form, useLocation } from "react-router";
export function CookieBanner() {
const { pathname } = useLocation();
return (
<div>
<p>We're using cookies</p>
<p>...</p>
<Form method="POST" action="/?index">
<input type="hidden" name="redirect-to" value={pathname} />
<button type="submit" name="consent" value="all">
Accept all
</button>
<button type="submit" name="consent" value="necessary">
Only necessary
</button>
</Form>
</div>
);
}
Using the pathname
value, we include a hidden input field named redirect-to
. This ensures the form submits the current pageâs URL.
Next, letâs modify the index routeâs action
to handle this value and redirect the user appropriately:
// app/routes/home.tsx
import { redirect } from "react-router";
import type { Route } from "./+types/home";
export async function action({ request }: Route.ActionArgs) {
const formData = await request.formData();
const redirectTo = String(formData.get("redirect-to") || "/");
// Handle user preferences here
return redirect(redirectTo);
}
With this setup, users will be redirected back to the page they were on when they submitted their cookie preferences.
Step 6: Conditional Rendering of the Cookie Banner
Another important improvement is ensuring the cookie banner is dismissed once the user interacts with it. We donât want the banner to reappear every time the page reloads. To achieve this, we can use a loader
in the layout to fetch user preferences and render the banner conditionally.
Hereâs an example implementation:
// app/layouts/layout.tsx
import { Outlet } from "react-router";
import { CookieBanner } from "~/components/cookie-banner";
import type { Route } from "./+types/layout";
export function loader() {
return { consent: "unset" }; // Fetch user preferences and return them here
}
export default function Layout({ loaderData }: Route.ComponentProps) {
return (
<>
<header>...</header>
<main>
<Outlet />
</main>
<footer>...</footer>
{loaderData.consent === "unset" && <CookieBanner />}
</>
);
}
As you can see, the loader
fetches the userâs cookie consent preferences (this logic will depend on your implementation). If the user has already provided consent, the cookie banner will not render. If no consent has been provided (consent: "unset"
), the banner is displayed.
This ensures the bannerâs behavior is consistent across different routes and reloads.
Wrapping Up
In this article, we implemented a cookie banner in a React Router v7 app that seamlessly works across all routes. We tackled:
- Adding a layout component and integrating a cookie banner into it.
- Handling form submissions and redirecting users back to their previous page.
- Conditionally rendering the cookie banner based on user preferences.
By using React Routerâs loader
and action
mechanisms, we achieved a clean and user-friendly solution. If youâd like a follow-up article exploring how to persist cookie preferences (e.g., using local storage, an API, or cookies), let me know in the comments - Iâd be happy to dive deeper into the topic.
Thanks for reading!
Top comments (0)