promise-render
v0.1.2
Published
Render React components as async functions — _await user interaction_ just like fetching data.
Readme
promise-render
Render React components as async functions — await user interaction just like fetching data.
promise-render is a tiny utility that lets you render a React component imperatively and wait for its result via a Promise.
Perfect for confirmation dialogs, pickers, wizards, forms, and any UI that needs to “return a value” to your async code.
🚀 Features
- Render any React component and get its output via a
Promise - Call UI from thunks, sagas, services, or any async function
- No context, no event bus, no global stores
- Component automatically unmounts after resolve/reject
- TypeScript support included
📦 Installation
npm install promise-render
# or
yarn add promise-render🧠 Core Concept
promiseRender(Component) returns a pair:
const [ asyncFunction, AsyncComponent ] = promiseRender(Component)AsyncComponentis a React component you render once (e.g. in a modal root).asyncFunction(props?)renders that component and returns aPromise.- Inside the component, two special props are available:
resolve(value)— resolves the promise & unmounts the componentreject(error)— rejects the promise & unmounts the component
This allows you to control UI flow like this:
const result = await asyncFunction(props);🏎️ Quick Example
import { promiseRender } from 'promise-render';
const ConfirmDelete = ({ resolve }) => (
<Modal>
<p>Delete user?</p>
<button onClick={() => resolve(true)}>Yes</button>
<button onClick={() => resolve(false)}>No</button>
</Modal>
);
const [confirmDelete, ConfirmDeleteAsync] = promiseRender<boolean>(ConfirmDelete);
// Render the async component once in your root:
function App() {
return (
<>
<MainApp />
<ConfirmDeleteAsync />
</>
);
}
// Now you can await UI from anywhere:
async function onDeleteClick() {
const confirmed = await confirmDelete();
if (!confirmed) return;
await api.deleteUser();
}- ✅ No global event emitters.
- ✅ No prop drilling.
- ✅ Just await your UI.
🎛️ Passing Props
You can pass any props to the async function. They will be forwarded to the component automatically.
import { promiseRender } from 'promise-render';
const ConfirmAlert = ({ resolve, text }) => (
<Modal>
<p>{text}</p>
<button onClick={() => resolve(true)}>Yes</button>
<button onClick={() => resolve(false)}>No</button>
</Modal>
);
const [confirm, ConfirmAlertAsync] = promiseRender<boolean>(ConfirmAlert);
// Render <ConfirmAlertAsync /> somewhere in your root
const deleteUser = createAsyncThunk('deleteUser', async () => {
const confirmed = await confirm({
text: "Are you sure you want to delete this user?",
});
if (!confirmed) return;
await api.deleteUser();
});🧰 Common Use Cases
- ✅ Confirmation dialogs
- ✅ Pickers / Select modals
- ✅ Login or consent popups
- ✅ Form dialogs that “return” values
- ✅ Wizards or multi-step flows
- ✅ Any async interaction that should “pause” logic until user acts
If you ever wished JavaScript had await confirmDialog() — now it does.
🧪 Advanced Example: Returning Form Data
const NamePrompt = ({ resolve }) => {
const [name, setName] = useState("");
return (
<Modal>
<input value={name} onChange={e => setName(e.target.value)} />
<button onClick={() => resolve(name)}>Submit</button>
</Modal>
);
};
const [promptName, NamePromptAsync] = promiseRender<string>(NamePrompt);
async function createItem() {
const name = await promptName();
await api.createItem({ name });
}🧹 Cleanup & Unmounting
promise-render handles lifecycle automatically:
- Component mounts when the async function is called
- Component unmounts when resolve or reject is triggered
- The same mounted component instance is reused between calls
No global state or manual cleanup required.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Feel free to extend or modify this README according to your preferences!
