Easy Labs
SDKsReactExamples

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:

  1. Loads the customer record on mount.
  2. Loads payment instruments, orders, and subscriptions in parallel.
  3. Renders three tabs (or sections) for each list.
  4. 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

src/components/CustomerDashboard.tsx
"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/node and pass it to a client component as props.
  • No built-in pagination. getCustomerOrders and getCustomerSubscriptions accept 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. updateCustomer returns 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 instruments loaded, pass an instrument.id directly to checkout as source: instrument.id. This skips tokenization entirely — no <CardElement> needed for repeat purchases. The Next.js example shows the full pattern in src/app/checkout/page.tsx.

On this page