Skip to main content

once()

once<Args, Result>(fn): (...args) => Result

Creates a function that can only be called once.

note

Subsequent calls return the cached result and ignore new arguments.


Type Parameters​

Args: Args extends unknown[]​

The argument types of the function.

Result: Result​

The return type of the function.


Parameters​

fn: (...args) => Result​

The function to restrict to one execution.


Returns​

The restricted function that caches the first call's result.


Since​

2.0.0


Performance​

O(1) lookup after first call. Single boolean flag check for optimal performance.


Also known as​

once (Lodash, es-toolkit, Remeda, Radashi, Ramda) · ❌ (Effect, Modern Dash, Antfu)


Example​

// Basic usage
const initialize = once(() => {
console.log('Initializing...');
return 'initialized';
});

initialize(); // "Initializing..." -> "initialized"
initialize(); // "initialized" (cached)

// With parameters (ignores new args after first call)
const createUser = once((name: string) => ({ id: 1, name }));

createUser('John'); // { id: 1, name: 'John' }
createUser('Jane'); // { id: 1, name: 'John' } (cached)

// Async functions
const fetchData = once(async () => fetch('/api/data'));
await fetchData(); // Fetches
await fetchData(); // Cached promise

How it works?​

Creates a function that executes only once β€” subsequent calls return the cached result.

Simple Flow​

Key Behavior​

New arguments are ignored after the first call:


Use Cases​

Initialize resources only once πŸ“Œβ€‹

Ensure initialization code runs only once to prevent duplicate setup. Essential for resource management and preventing initialization errors.

const initApp = once(() => {
console.log("App initialized");
// ... setup code
});

initApp(); // "App initialized"
initApp(); // (ignored)

Create singletons safely​

Ensure singleton instances are created only once. Essential for state management and ensuring single instance patterns.

const getDb = once(() => new DatabaseConnection());
const db1 = getDb();
const db2 = getDb();

console.log(db1 === db2); // true

Lazy-initialize an SDK or API client​

Initialize a third-party SDK on first use instead of at app startup. The standard pattern for Stripe, Firebase, Analytics, or any heavy client library.

const getStripe = once(() => {
return loadStripe(process.env.STRIPE_PUBLIC_KEY);
});

const getAnalytics = once(() => {
return initializeAnalytics({ trackingId: "UA-XXXXX" });
});

// Called from multiple components β€” SDK loads only on first call
async function handleCheckout() {
const stripe = await getStripe();
stripe.redirectToCheckout({ sessionId });
}

Request browser permission only once​

Ensure a permission prompt is shown only once per session. Critical for notifications, geolocation, and camera access in PWAs.

const requestNotificationPermission = once(async () => {
const result = await Notification.requestPermission();
return result === "granted";
});

// Multiple components can call this safely
async function enableNotifications() {
const granted = await requestNotificationPermission();
if (granted) {
subscribeToUpdates();
}
}

Initialize focus trap on a dialog element​

Set up focus trapping once when a modal opens to keep keyboard navigation inside. Critical for accessible dialogs and overlays in design systems.

// Use once PER dialog instance, not globally
const createFocusTrap = (dialog: HTMLElement) => {
const init = once(() => {
const focusableEls = dialog.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const firstEl = focusableEls[0] as HTMLElement;
const lastEl = focusableEls[focusableEls.length - 1] as HTMLElement;

dialog.addEventListener("keydown", (e: KeyboardEvent) => {
if (e.key !== "Tab") return;
if (e.shiftKey && document.activeElement === firstEl) {
e.preventDefault();
lastEl.focus();
} else if (!e.shiftKey && document.activeElement === lastEl) {
e.preventDefault();
firstEl.focus();
}
});

firstEl.focus();
return { firstEl, lastEl };
});

return init;
};

// Each dialog gets its own once-guarded init
const trap = createFocusTrap(dialogElement);
trap(); // Initializes
trap(); // Ignored (already initialized)

Create a live announcer singleton for screen readers​

Initialize an ARIA live region once for dynamic announcements. Essential for SPAs where route changes and async updates need to be announced.

const getLiveAnnouncer = once(() => {
const el = document.createElement("div");
el.setAttribute("aria-live", "polite");
el.setAttribute("aria-atomic", "true");
el.className = "sr-only";
document.body.appendChild(el);
return el;
});

const announce = (message: string) => {
const announcer = getLiveAnnouncer();
announcer.textContent = "";
requestAnimationFrame(() => {
announcer.textContent = message;
});
};

// Route change
announce("Dashboard page loaded");
// Async operation
announce("3 new notifications");

Detect platform capabilities once at startup​

Run platform detection once and cache the result for the app lifetime. Essential for cross-browser components that adapt to the environment.

const detectPlatform = once(() => ({
isTouchDevice: "ontouchstart" in window || navigator.maxTouchPoints > 0,
supportsPassive: (() => {
let passive = false;
try {
const opts = Object.defineProperty({}, "passive", { get: () => (passive = true) });
window.addEventListener("test", null as never, opts);
} catch {}
return passive;
})(),
prefersReducedMotion: window.matchMedia("(prefers-reduced-motion: reduce)").matches,
isHighContrast: window.matchMedia("(forced-colors: active)").matches,
}));

// Used across the entire design system
const platform = detectPlatform();
if (platform.prefersReducedMotion) {
disableAnimations();
}

Initialize clipboard listener once​

Set up clipboard event handling once for a rich text editor. Perfect for editors and code blocks with custom copy/paste behavior.

const initClipboard = once((editor: HTMLElement) => {
editor.addEventListener("copy", (e: ClipboardEvent) => {
const selection = window.getSelection()?.toString() ?? "";
e.clipboardData?.setData("text/plain", selection);
e.clipboardData?.setData("text/html", editor.innerHTML);
e.preventDefault();
});

editor.addEventListener("paste", (e: ClipboardEvent) => {
const html = e.clipboardData?.getData("text/html");
const text = e.clipboardData?.getData("text/plain") ?? "";
insertContent(html ?? text);
e.preventDefault();
});
});

// Safe to call on every editor mount
initClipboard(editorElement);

Register a service worker once at startup​

Register the service worker only on first call, even if multiple modules trigger it. Essential for PWA initialization without duplicate registrations.

const registerSW = once(async () => {
if ("serviceWorker" in navigator) {
const registration = await navigator.serviceWorker.register("/sw.js");
console.log("SW registered:", registration.scope);
return registration;
}
return null;
});

// Safe to call from multiple entry points
await registerSW();