We can finally put all together inside the route component.
The route is defined inside routes.ts
and accepts a slug
parameter (checkout/:slug
):
import type { RouteConfig } from "@react-router/dev/routes";
import { route } from "@react-router/dev/routes";
export const routes: RouteConfig = [route("checkout/:slug", "routes/home.tsx")];
Inside routes/home.tsx
we use a React Router loader
to load the client token from an environment variable using Config
:
export async function loader() {
return Effect.runPromise(
Config.all({ paddleClientToken: Config.string("PADDLE_CLIENT_TOKEN") })
);
}
With React Router v7 the result of loader
is passed to the route component as props
(loaderData
):
import { Config, Effect } from "effect";
// 👇 Route types generated by React Router v7
import type * as Route from "./+types.home";
export async function loader() {
return Effect.runPromise(
Config.all({ paddleClientToken: Config.string("PADDLE_CLIENT_TOKEN") })
);
}
export default function Index({
loaderData,
params: { slug },
}: Route.ComponentProps) {
// ...
}
Inside the component we initialize the state machine with useMachine
, passing as input slug
and clientToken
:
export default function Index({
loaderData,
params: { slug },
}: Route.ComponentProps) {
const [snapshot] = useMachine(machine, {
input: { clientToken: loaderData.paddleClientToken, slug },
});
// ...
}
The actual JSX contains the Breadcrumbs
component as well as a div
where the Paddle inline checkout will be rendered:
Breadcrumb
usessnapshot.matches
to check and highlight the current step based on the machine's state.
export default function Index({
loaderData,
params: { slug },
}: Route.ComponentProps) {
const [snapshot] = useMachine(machine, {
input: { clientToken: loaderData.paddleClientToken, slug },
});
return (
<main className="mx-auto max-w-4xl my-12">
<Breadcrumbs className="mb-8">
<Breadcrumb data-selected={snapshot.matches("Customer")}>
Customer
</Breadcrumb>
<Breadcrumb data-selected={snapshot.matches("Checkout")}>
Checkout
</Breadcrumb>
<Breadcrumb data-selected={snapshot.matches("Success")}>
Success
</Breadcrumb>
</Breadcrumbs>
<div className={PADDLE_CONTAINER_CLASS}></div>
</main>
);
}
The checkout is ready! Running the app will do the following:
loader
loads theclientToken
as an environment variable- The state machine will call the API to load the product from
slug
- Initialize the checkout by calling
initializePaddle
andPaddle.Checkout.open
- The checkout is rendered inside the
div
with thePADDLE_CONTAINER_CLASS
class - Each step of the checkout is synced with the state machine, updating the
Breadcrumbs
component accordingly - The user will proceed with the payment until the last "Success" step