Promise-render – Render React components as async functions

2 hours ago 2

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.

  • 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
npm install promise-render # or yarn add promise-render

promiseRender(Component) returns a pair:

const [ asyncFunction, AsyncComponent ] = promiseRender(Component)
  • AsyncComponent is a React component you render once (e.g. in a modal root).
  • asyncFunction(props?) renders that component and returns a Promise.
  • Inside the component, two special props are available:
  • resolve(value) — resolves the promise & unmounts the component
  • reject(error) — rejects the promise & unmounts the component

This allows you to control UI flow like this:

const result = await asyncFunction(props);
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.

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(); });
  • ✅ 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 }); }

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.

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!

Read Entire Article