Show React components and
awaita result – turn any component (modals, dialogs, prompts, ...) into an async function
npm install awaitable-component
# or
yarn add awaitable-component
# or
pnpm add awaitable-componentRequires: React >= 18.0.0
Use the default AwaitableComponent instance (or create your own instances).
import { AwaitableComponent } from 'awaitable-component';
function App() {
return (
<>
<AwaitableComponent.Root />
<YourApp />
</>
);
}import { AwaitableComponentProps } from 'awaitable-component';
// Example with MUI dialog API
function ConfirmDialog({ title, message, onExitComplete, onSubmit, onDismiss, isVisible }: AwaitableComponentProps<boolean> & {
title: string;
message: string;
}) {
return (
<Dialog open={isVisible} onTransitionExited={onExitComplete}>
<h2>{title}</h2>
<p>{message}</p>
<button onClick={() => onDismiss()}>Cancel</button>
<button onClick={() => onSubmit(true)}>Confirm</button>
</Dialog>
);
}Use show() to simply call and await the result, or use showControlled() for control over the component lifecycle.
async function handleDelete() {
const confirmed = await AwaitableComponent.show(ConfirmDialog, {
title: 'Delete Item',
message: 'Are you sure?'
});
if (confirmed) {
await deleteItem();
}
}Use show() to simply await the result of a component.
const result = await AwaitableComponent.show(MyComponent, {
prop1: 'value'
}, {
id: 'unique-id', // Optional: reuse/update existing component
scope: 'custom', // Optional: target specific root
exitTimeout: 300 // Optional: override default timeout
});Use showControlled() to have control over the component lifecycle.
async function uploadFile(file: File) {
const controlled = AwaitableComponent.showControlled(ProgressDialog, {
title: 'Uploading...',
progress: 0
});
for (let i = 0; i <= 100; i += 10) {
await delay(100);
controlled.update({ progress: i }); // Update props while visible (partial props supported)
}
controlled.submit({ success: true }); // Programmatically submit
return controlled.promise;
}Partial prop updates:
The update() method accepts partial props, allowing you to update only specific properties:
const controlled = AwaitableComponent.showControlled(StatusDialog, {
title: 'Processing...',
message: 'Starting upload',
progress: 0
});
// Update only progress
controlled.update({ progress: 50 });
// Update only message
controlled.update({ message: 'Finalizing...' });
// Update multiple props
controlled.update({ progress: 100, message: 'Complete!' });Create bound versions of components for simple reuse:
// Bind a confirmation dialog for easy reuse
const ConfirmDialog = AwaitableComponent.create(Dialog);
// Use it anywhere without passing the Dialog component
async function handleDelete() {
const confirmed = await ConfirmDialog.show({
title: 'Delete Item',
message: 'Are you sure?'
});
if (confirmed) {
await deleteItem();
}
}
async function handleLogout() {
const confirmed = await ConfirmDialog.show({
title: 'Logout',
message: 'Are you sure you want to logout?'
});
if (confirmed) {
await logout();
}
}Bound component root placement:
By default, bound components render in their parent's AwaitableComponent.Root. However, they provide their own root which can be rendered anywhere in your app for specific DOM placement:
// Default: renders in parent root
function App() {
return (
<>
<AwaitableComponent.Root /> {/* ConfirmDialog renders here by default */}
<YourApp />
</>
);
}
// Optional: render in specific location
function SomePlaceInYourApp() {
return (
<>
<ConfirmDialog.Root /> {/* ConfirmDialog renders here instead */}
{/* ... */}
</>
);
}Create separate AwaitableComponent instances.
import { AwaitableComponentClass } from 'awaitable-component';
const UserAppAwaitableComponent = new AwaitableComponentClass(500); // Optional exit timeout
const AdminAppAwaitableComponent = new AwaitableComponentClass();
function UserApp() {
return (
<>
<UserAppAwaitableComponent.Root />
<UserContent />
</>
);
}
function AdminApp() {
return (
<>
<AdminAppAwaitableComponent.Root />
<AdminContent />
</>
);
}Built-in lifecycle hooks for smooth transitions. Use onExitComplete to hide the component after the exit animation completes, and/or define an exitTimeout to remove the component after a specified timeout. Set the timeout to 0 to remove the component immediately after dismissal or submission.
function AnimatedDialog({
isVisible,
onExitComplete,
onSubmit,
onDismiss
}: AwaitableComponentProps<string>) {
return (
<Dialog
open={isVisible}
onTransitionEnd={onExitComplete} // ✓ Preferred: precise control
className={isVisible ? 'fade-in' : 'fade-out'}
>
<button onClick={() => onSubmit('confirmed')}>OK</button>
</Dialog>
);
}Animation lifecycle:
- Component renders with
isVisible: true - User action triggers
onSubmitoronDismiss - Component re-renders with
isVisible: false(trigger exit animation) - Component unmounts when whichever comes first:
onExitComplete()is called (precise control, best result)exitTimeoutexpires
Exit timeout configuration:
const AwaitableDialog = new AwaitableComponentClass(300); // Fallback for everything
await AwaitableDialog.show(AnimatedDialog, props, {
exitTimeout: 300 // Overrides all defaults
});
const BoundDialog = AwaitableDialog.create(AnimatedDialog, {
scope: 'my-scope',
exitTimeout: 250 // Default for all BoundDialog.show() calls
});The library exports a default instance for immediate use.
It has the exitTimeout disabled and requires onExitComplete to be called or a exitTimeout to be passed on each show call to hide components.
import { AwaitableComponent } from 'awaitable-component';Create separate instances for architectural separation:
import { AwaitableComponentClass } from 'awaitable-component';
const UserAppDialogs = new AwaitableComponentClass(250);
const AdminAppDialogs = new AwaitableComponentClass(300);Parameters:
exitTimeout(optional) - Default exit timeout for this instance (default:null(disabled))
Each instance has its own Root, show(), showControlled() and create() methods.
Root component that renders active components. Mount once in your app:
<AwaitableComponent.Root />Show a component and await its result:
const result = await AwaitableComponent.show(MyComponent, {
prop1: 'value'
}, {
id: 'unique-id', // Optional: reuse/update existing component
scope: 'custom', // Optional: target specific bound component
exitTimeout: 300 // Optional: override exit timeout (highest priority)
});Returns: Promise<T> that resolves on submitting and rejects on dismissing
Options:
exitTimeout(optional) - Overrides bound component and global defaultsscope(optional) - Target specific bound componentid(optional) - Unique component identifier
Show with programmatic control:
const controlled = AwaitableComponent.showControlled(MyComponent, props);
controlled.update({ newProp: 'value' }); // Update props
controlled.submit(result); // Resolve promise
controlled.dismiss(error); // Reject promise
await controlled.promise; // Access underlying promiseReturns: ControlledAwaitableComponent
update(props)- Update props (accepts partial props)submit(result)- Resolve promisedismiss(error)- Reject promisepromise- Access underlying promise
Options:
exitTimeout(optional) - Overrides bound component and global defaultsscope(optional) - Target specific bound componentid(optional) - Unique component identifier
Create a bound component for easy reuse:
const ConfirmDialog = AwaitableComponent.create(Dialog, {
id: 'confirm-dialog', // Optional: unique bound component identifier (auto-generated if omitted)
exitTimeout: 300 // Optional: default for all ConfirmDialog.show() calls
});
// Bound root is optional - if not rendered, uses parent AwaitableComponent.Root
<ConfirmDialog.Root /> // Optional: render in specific location
await ConfirmDialog.show(props); // Show component
ConfirmDialog.showControlled(props); // Show with controlOptions:
id(optional) - Unique bound component identifier (auto-generated if not provided)exitTimeout(optional) - Default exit timeout for allshow()calls on this bound component (overrides instance default, can be overridden per-call)
This timeout is used as a fallback when onExitComplete is not called. It has the lowest priority and is overridden by:
- Per-call
exitTimeoutinshow()options - Bound component
exitTimeoutincreate()options
Your components should extend AwaitableComponentProps<T>:
interface AwaitableComponentProps<T = void> {
onSubmit: (value: T) => void; // Resolve the promise
onDismiss: (reason?: any) => void; // Reject the promise
isVisible: boolean; // False during exit animation
onExitComplete?: () => void; // Preferred: call when animation finishes
}Animation props:
isVisible- Toggles tofalsewhen component should exit (use this to trigger your exit animation)onExitComplete- Call this when your exit animation completes for precise timing (preferred over relying onexitTimeout)
MIT