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, route } from "@react-router/dev/routes";
export default [
route("checkout/:slug", "routes/home.tsx"),
] satisfies RouteConfig;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 { 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:
Breadcrumbusessnapshot.matchesto 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:
loaderloads theclientTokenas an environment variable- The state machine will call the API to load the product from
slug - Initialize the checkout by calling
initializePaddleandPaddle.Checkout.open - The checkout is rendered inside the
divwith thePADDLE_CONTAINER_CLASSclass - Each step of the checkout is synced with the state machine, updating the
Breadcrumbscomponent accordingly - The user will proceed with the payment until the last "Success" step
