Using the ISA SDK with React
Initialize the ISA SDK once, share it through context, and run quotes from React 18 hooks.
Using the ISA SDK with React
A React 18 integration of the ISA SDK. Initialize one client per app, share it through context, and run quotes from custom hooks. The SDK ships no React-specific surface — these patterns work for any async client with hooks and Suspense.
Install
npm install isa-sdk
The SDK targets Node 20+ and React 18+. It works in Vite, Create React App, and Next.js.
Bearer tokens must stay on the server. Never ship your token to the browser — a token in client JavaScript can be copied by anyone. Run the SDK on a backend (Node API route, edge function, or BFF) and have React call your backend endpoint instead. The examples below use a server proxy at
/api/quotewhere theIsaclient lives.If you use Next.js App Router, see the Next.js guide — server components keep the token server-side for you.
Initialize the client (on your backend)
Create one Isa instance per server process. Isa.fromEnv() reads ISA_TOKEN from the environment — your isa_test_… or isa_live_… key. This keeps the token out of source code.
// server/isa.ts (Node — Express, Fastify, or any framework)
import { Isa } from 'isa-sdk';
// Reads ISA_TOKEN from the environment. To pass the token explicitly:
// await Isa.withBearer({ token: process.env.ISA_TOKEN! });
export const isa = await Isa.fromEnv();
Your token arrives by email after checkout. To rotate it, contact support (a self-serve dashboard ships later). The SDK is concurrency-safe — share the single instance across all requests.
Load reference data
Expose isa.zyins.datasets.get() from your backend as an endpoint (e.g. GET /api/datasets). React components fetch from your endpoint, not the SDK directly.
Cache the response with a 24-hour stale time using TanStack Query or SWR. Datasets change rarely — only when carriers are added or rates update.
Run a quote
The canonical happy path: John Doe, born 1962-04-18, NC, 70 inches, 195 lb,
no conditions or medications, looking at Aetna Accendo final expense at
$25,000 of coverage.
The backend builds the request from typed value objects, runs the quote,
and forwards result.data — a { plans } envelope — to the React app.
Backend: POST /api/quote
POST /api/quote// server/routes/quote.ts (Express; same pattern in Fastify, Hono, etc.)
import { Router } from 'express';
import {
Isa, Sex, State, Height, Weight, NicotineDuration, Coverage, Products, ProductSelection,
} from 'isa-sdk';
const isa = await Isa.withBearer(); // share this instance across requests
export const quoteRouter = Router().post('/quote', async (_req, res, next) => {
try {
const { data } = await isa.zyins.prequalify({
applicant: {
dob: '1962-04-18',
sex: Sex.Male,
height: Height.fromInches(70),
weight: Weight.fromPounds(195),
state: State.NorthCarolina,
nicotineUse: { lastUsed: NicotineDuration.Never },
},
coverage: Coverage.faceValue(25_000),
products: ProductSelection.of([Products.Fex.AetnaAccendo]),
});
res.json(data); // forwards { plans: V3Offer[] }
} catch (err) { next(err); }
});
Coverage.faceValue(25_000) means $25,000 of death benefit. The typed State.NorthCarolina catches misspellings at compile time, not at runtime.
Frontend: React component
The backend forwards the plain v3 JSON. Each offer has a carrier, a product, and a pricing array of rate-class rows. Find the row where primary: true — that's the headline rate to display.
// src/components/QuoteButton.tsx
import { useState } from 'react';
interface PricingRow {
primary: boolean;
rate_class: string;
eligibility: { category: 'immediate' | 'graded' | 'rop'; eligible: boolean };
premium: { amount: { cents: number; display: string }; default_mode: string };
}
interface PlanOffer {
carrier: { id: string; name: string };
product: { id: string; name: string };
pricing: PricingRow[];
}
const headline = (offer: PlanOffer) =>
offer.pricing.find((row) => row.primary) ?? offer.pricing[0];
export function QuoteButton() {
const [plans, setPlans] = useState<PlanOffer[]>([]);
const [loading, setLoading] = useState(false);
async function runQuote() {
setLoading(true);
try {
const r = await fetch('/api/quote', { method: 'POST' });
const data: { plans: PlanOffer[] } = await r.json();
setPlans(data.plans);
} finally {
setLoading(false);
}
}
return (
<>
<button onClick={runQuote} disabled={loading}>
{loading ? 'Running…' : 'Run quote'}
</button>
<ul>
{plans.map((offer) => {
const row = headline(offer);
return (
<li key={offer.product.id}>
{offer.carrier.name} — {offer.product.name} —
{' '}{row.eligibility.category} — {row.premium.amount.display}
</li>
);
})}
</ul>
</>
);
}
premium.amount.cents is the canonical integer in US cents. premium.amount.display is the carrier-formatted string — use this for display.
Do arithmetic on cents, not on display. premium.default_mode tells you which premium.modes key the headline amount came from.
The SDK mints an idempotency key automatically per call.
Handle errors
Every SDK exception extends IsaError. Match on the specific subclass —
never switch on error.message.
// src/isa/IsaErrorBoundary.tsx
import { Component, type ReactNode } from 'react';
import {
IsaApiError,
IsaConfigError,
IsaIdempotencyConflictError,
} from 'isa-sdk';
interface Props { children: ReactNode; }
interface S { message?: string; requestId?: string; }
export class IsaErrorBoundary extends Component<Props, S> {
state: S = {};
static getDerivedStateFromError(err: unknown): S {
if (err instanceof IsaIdempotencyConflictError) {
return { message: `Duplicate request with a different body (${err.key}).` };
}
if (err instanceof IsaApiError) {
return { message: err.message, requestId: err.requestId };
}
if (err instanceof IsaConfigError) {
return { message: 'SDK configuration error. Check ISA_TOKEN on the server.' };
}
return { message: 'An unexpected error occurred.' };
}
render() {
if (this.state.message) {
return (
<div role="alert">
<p>{this.state.message}</p>
{this.state.requestId && <small>request {this.state.requestId}</small>}
</div>
);
}
return this.props.children;
}
}
Always log err.requestId — that's the support ticket's anchor.
Production checklist
ISA_TOKENset on the server only — never bundled into the React app. A
bearer token in client JavaScript is a token anyone can copy.- The React app calls a backend endpoint (e.g.
/api/quote); the SDK
lives behind that route. - Idempotency keys are UUID v4 (the SDK default). If you mint your own,
match550e8400-e29b-41d4-a716-446655440000. result.requestIdlogged on every error (req_01HZK2N5GQR9T8X4B6FJW3Y1AS).- Datasets fetched once on bootstrap, cached via TanStack Query or SWR.
- An
<IsaErrorBoundary>at the page level so server-error responses
surface to the user without crashing the route.
What's next
- Authentication — bearer token rotation,
test vs. live - Idempotency — how the SDK mints keys, when to
bring your own - Errors reference — every
codevalue and remediation - Next.js guide — server-only credentials and
Server Actions - TypeScript Quickstart — the underlying
SDK shapes
Updated about 10 hours ago