once()
once<
Args,Result>(fn): (...args) =>Result
Creates a function that can only be called once.
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();