Customer Management
Build a customer self-service area with the React SDK.
This recipe builds the data layer for a customer-facing account page: list the signed-in customer's saved payment methods, orders, and subscriptions, then patch their profile in place. Everything runs through useEasy() from inside React components — no extra REST plumbing.
Goal
We want a <CustomerDashboard customerId={...}> component that:
- Loads the customer record on mount.
- Loads payment instruments, orders, and subscriptions in parallel.
- Renders three tabs (or sections) for each list.
- Lets the user update name / email and persists with
updateCustomer.
This mirrors examples/next-example/src/app/profile/page.tsx, simplified.
Implementation
1. Create the customer
Customers are usually created at signup or at first checkout. For a standalone signup form:
const { createCustomer } = useEasy();
const res = await createCustomer({
first_name: "Ada",
last_name: "Lovelace",
email: "ada@example.com",
});
const customerId = res.data.id;Persist customerId somewhere your app can read it back — most apps store it on their own user row.
2. Load the dashboard data in parallel
"use client";
import {
useEasy,
type CustomerData,
type OrderData,
type PaymentInstrumentData,
type SubscriptionData,
} from "@easylabs/react";
import { useEffect, useState } from "react";
type State = {
customer: CustomerData | null;
instruments: PaymentInstrumentData[];
orders: OrderData[];
subscriptions: SubscriptionData[];
loading: boolean;
error: string | null;
};
export function CustomerDashboard({ customerId }: { customerId: string }) {
const {
getCustomer,
getCustomerPaymentInstruments,
getCustomerOrders,
getCustomerSubscriptions,
updateCustomer,
} = useEasy();
const [state, setState] = useState<State>({
customer: null,
instruments: [],
orders: [],
subscriptions: [],
loading: true,
error: null,
});
useEffect(() => {
let cancelled = false;
(async () => {
try {
const [customer, instruments, orders, subscriptions] = await Promise.all([
getCustomer(customerId),
getCustomerPaymentInstruments(customerId),
getCustomerOrders(customerId),
getCustomerSubscriptions(customerId),
]);
if (cancelled) return;
setState({
customer: customer.success ? customer.data : null,
instruments: instruments.success ? instruments.data : [],
orders: orders.success ? orders.data : [],
subscriptions: subscriptions.success ? subscriptions.data.data : [],
loading: false,
error: customer.success ? null : customer.message,
});
} catch (err) {
if (cancelled) return;
setState((s) => ({
...s,
loading: false,
error: err instanceof Error ? err.message : "Failed to load.",
}));
}
})();
return () => {
cancelled = true;
};
}, [customerId, getCustomer, getCustomerPaymentInstruments, getCustomerOrders, getCustomerSubscriptions]);
async function saveProfile(patch: Partial<CustomerData>) {
const res = await updateCustomer(customerId, patch);
if (res.success) {
setState((s) => ({ ...s, customer: res.data }));
}
}
if (state.loading) return <p>Loading…</p>;
if (state.error || !state.customer) return <p>Couldn't load account.</p>;
return (
<>
<ProfileSection customer={state.customer} onSave={saveProfile} />
<PaymentMethodsSection instruments={state.instruments} />
<OrdersSection orders={state.orders} />
<SubscriptionsSection subscriptions={state.subscriptions} />
</>
);
}(ProfileSection, PaymentMethodsSection, etc. are plain presentational components that render the typed records. Use whatever UI kit you prefer.)
3. Cancel a subscription
Cancellation is a one-liner. Refetch the list afterward (or optimistically update local state).
const { cancelSubscription, getCustomerSubscriptions } = useEasy();
async function onCancel(subscriptionId: string) {
await cancelSubscription(subscriptionId);
const fresh = await getCustomerSubscriptions(customerId);
if (fresh.success) setSubscriptions(fresh.data.data);
}Tradeoffs
- Client-side fetching. Everything runs in the browser with the user's session. That's fine for a logged-in dashboard. For SEO-sensitive pages, fetch the same data server-side via
@easylabs/nodeand pass it to a client component as props. - No built-in pagination.
getCustomerOrdersandgetCustomerSubscriptionsaccept pagination params (limit,cursor). For long histories, page them — see the Node SDK reference for the exact param shape (the React types mirror the server's directly). - Stale data after mutation.
updateCustomerreturns the patched record; reuse that instead of refetching. For lists (getCustomerPaymentInstruments, etc.) you'll want to refetch or optimistically update. - Saved-instrument selection at checkout. Once you have
instrumentsloaded, pass aninstrument.iddirectly tocheckoutassource: instrument.id. This skips tokenization entirely — no<CardElement>needed for repeat purchases. The Next.js example shows the full pattern insrc/app/checkout/page.tsx.