Element Lifecycle
Elements move through a series of stages from creation to cleanup. Understanding this flow helps you write correct initialization code and avoid common pitfalls.
initialization → creation → mounting → ready → runtime → unmounting
This page explains when each stage occurs and what triggers it. For the full API reference for every element method, see Methods.
Stage 1: Initialization
Create a single SDK instance and share it throughout your application.
- Web Elements
- React Elements
import BasisTheory from '@basis-theory/web-elements';
// Synchronous — returns immediately, no await needed
const bt = BasisTheory('<PUBLIC_API_KEY>');
import { BasisTheoryProvider } from '@basis-theory/react-elements';
function App() {
return (
<BasisTheoryProvider apiKey="<PUBLIC_API_KEY>">
{/* all components that use elements go here */}
</BasisTheoryProvider>
);
}
Stage 2: Creation
Create element instances. This is also synchronous and fast — no network requests happen at this point.
- Web Elements
- React Elements
const cardNumber = bt.createElement('cardNumber', {
placeholder: '4111 1111 1111 1111',
});
const expiry = bt.createElement('expiry', {
placeholder: 'MM / YY',
});
const cvv = bt.createElement('cvv', {
placeholder: 'CVV',
});
Valid element types are: 'cardNumber', 'expiry', 'cvv', 'text'.
In React, elements are created automatically when their components are rendered. You interact with them via refs:
import { useRef } from 'react';
import {
CardNumberElement,
ExpiryElement,
CVVElement,
} from '@basis-theory/react-elements';
function PaymentForm() {
const cardNumberRef = useRef(null);
const expiryRef = useRef(null);
const cvvRef = useRef(null);
return (
<>
<CardNumberElement id="card-number" ref={cardNumberRef} placeholder="4111 1111 1111 1111" />
<ExpiryElement id="expiry" ref={expiryRef} placeholder="MM / YY" />
<CVVElement id="cvv" ref={cvvRef} placeholder="CVV" />
</>
);
}
Stage 3: Mounting
Mount attaches the element's iframe to the DOM. You must await mount() — it resolves when the iframe has loaded and is ready for interaction.
- Web Elements
- React Elements
// Mount each element to its container
await cardNumber.mount('#card-number-container');
await expiry.mount('#expiry-container');
await cvv.mount('#cvv-container');
// Or mount in parallel
await Promise.all([
cardNumber.mount('#card-number-container'),
expiry.mount('#expiry-container'),
cvv.mount('#cvv-container'),
]);
You can pass a CSS selector string or a direct HTMLElement reference:
const container = document.getElementById('card-number-container');
await cardNumber.mount(container);
React handles mounting automatically when the component renders. The onReady prop fires when mounting is complete:
<CardNumberElement
id="card-number"
onReady={() => console.log('card number element is ready')}
/>
Stage 4: Ready
After mounting completes, the element fires a ready event. At this point the user can type into it and you can call focus/blur/clear.
- Web Elements
- React Elements
// Subscribe before mounting to avoid missing the event
cardNumber.on('ready', (event) => {
console.log('card number ready', event.detail.elementId);
// Safe to show the form, remove loading state, etc.
});
await cardNumber.mount('#card-number-container');
<CardNumberElement
id="card-number"
onReady={(event) => {
console.log('card number ready', event.detail.elementId);
}}
/>
Stage 5: Runtime
During the interactive phase, users type into elements. The SDK emits change, focus, blur, and error events. You can also call methods like update(), focus(), blur(), and clear().
- Web Elements
- React Elements
cardNumber.on('change', (event) => {
const { isValid, isEmpty, cardBrand } = event.detail;
submitButton.disabled = !isValid;
});
// Update options at any time
await cardNumber.update({ disabled: true });
// Programmatic focus
cardNumber.focus();
// Clear the input
cardNumber.clear();
import { useState } from 'react';
import { CardNumberElement } from '@basis-theory/react-elements';
function PaymentForm() {
const [isValid, setIsValid] = useState(false);
return (
<>
<CardNumberElement
id="card-number"
onChange={(event) => setIsValid(event.detail.isValid)}
/>
<button type="submit" disabled={!isValid}>
Pay
</button>
</>
);
}
For tokenization at this stage, see Services.
Stage 6: Unmounting
Call unmount() to remove the element's iframe from the DOM. This cleans up event listeners and frees resources. The element instance can be remounted later by calling mount() again.
- Web Elements
- React Elements
cardNumber.unmount();
expiry.unmount();
cvv.unmount();
// You can check whether an element is currently mounted
console.log(cardNumber.mounted); // false
React handles unmounting automatically when the component is removed from the tree (for example, when the user navigates away or a conditional rendering removes the element).
Full Web Elements example
import BasisTheory from '@basis-theory/web-elements';
async function initPaymentForm() {
// Stage 1: Initialization
const bt = BasisTheory('<PUBLIC_API_KEY>');
// Stage 2: Creation
const cardNumber = bt.createElement('cardNumber', { placeholder: '4111 1111 1111 1111' });
const expiry = bt.createElement('expiry', { placeholder: 'MM / YY' });
const cvv = bt.createElement('cvv', { placeholder: 'CVV' });
// Stage 4: Subscribe to ready before mounting
let readyCount = 0;
const onReady = () => {
readyCount++;
if (readyCount === 3) showForm();
};
cardNumber.on('ready', onReady);
expiry.on('ready', onReady);
cvv.on('ready', onReady);
// Stage 5: Runtime — track validity
let formValid = false;
cardNumber.on('change', (e) => {
formValid = e.detail.isValid;
document.getElementById('submit').disabled = !formValid;
});
// Stage 3: Mount all in parallel
await Promise.all([
cardNumber.mount('#card-number-container'),
expiry.mount('#expiry-container'),
cvv.mount('#cvv-container'),
]);
// Submit handler
document.getElementById('submit').addEventListener('click', async () => {
const token = await bt.tokens.create({
type: 'card',
data: {
number: cardNumber,
expiration_month: expiry,
expiration_year: expiry,
cvc: cvv,
},
});
console.log(token.id);
});
// Stage 6: Cleanup on page leave
window.addEventListener('beforeunload', () => {
cardNumber.unmount();
expiry.unmount();
cvv.unmount();
});
}
initPaymentForm();
Always unmount elements you no longer need to prevent memory leaks and dangling PostMessage listeners.