Magic Modallatest

A modal stack for React Native

React Native modals you can await

Mount one MagicModalPortal at the app root. Call show() from any async flow and await the typed result.

rating-flow.tsx
01const rating = await magicModal
  .show<RatingAnswer>(RatingModal).promise;02await magicModal.show(
  rating.data.score < 4
    ? FeedbackModal : StoreReviewModal
).promise;03await magicModal
  .show(ThanksModal).promise;04return rating;
promise pending
MagicModalPortalSTACK: 1 ENTRY
9:41
Moments
01RatingModal

Rate the app

How would you rate the app from zero to five?

Scores 0 through 3 open feedback. Scores 4 and 5 open the store prompt.
Why it exists
WHY IT EXISTS

Two callers can open a modal at the same time

The first Magic Modal flow asked for an app rating after a like. It could start from several screens and branch to feedback or the store. Both paths ended with a thank-you.

SCREEN OWNED4 pieces of visibility state

Every screen that can start the flow repeats the modal tree and its cleanup.

PORTAL OWNED4 callers share one function

The flow lives outside the screens. Each caller awaits the same sequence.

IDLE0 modal bodies mounted

The portal stays empty until show() runs.

OPENOne ID and promise per entry

Concurrent prompts keep their own place in the stack.

CODE4 callers share startRatingFlow()

The shared flow owns the modal components and visibility state.

See the portal tests
LIVE PACKAGE

Test two calls in one stack

Start the rating flow and stack a notification over it. Close the top entry and the first promise is still waiting underneath.

ACTUAL PACKAGEExpo / iOS / Android / Web
0open entries
ONE PORTAL ON THIS PAGE

Stack a notification over the rating prompt

Start the rating flow in the phone frame, or run update() in the web panel. This page mounts one MagicModalPortal for both.

CALLERrating-flow.ts
const result = await magicModal
  .show(RatingPrompt)
  .promise
1 portal1 result per entry
ACTIVE STACKTop entry stays visible
0

The stack is empty.Modal content mounts after show() runs.

PROMISE RESULTSEach promise resolves separately

No calls yet.Resolved values appear here.

COMMON FLOWS

Await a result or update the open modal

Each action resolves or updates the current modal. The result decides what opens next.

delete-post.tsx
const result = await magicModal
  .show<Confirmation>(() => (
    <DeletePostModal />
  ))
  .promise;

if (
  result.reason === INTENTIONAL_HIDE &&
  result.data.confirmed
) {
  await deletePost(postID);
}
MagicModalPortal1 entry

Delete this post?

This action cannot be undone.

RESULTPromise pending
APP ROOT

Mount one portal at the app root

Modal calls can come from anywhere in the app. Each show() gets an ID and promise, so one flow cannot overwrite the modal that was already open.

See the flow pattern
CALLERSpost.tsxcomment.tsxproduct.tsxnotifications.ts
FLOWstartRatingFlow()rating(); branch(); thanks();
APP ROOTMagicModalPortal
RETURN VALUE

What magicModal.show() returns

Await the close result, target this stack entry by ID, or replace its component while the same promise stays pending.

{
  promise: Promise<HideReturn<T>>;
  modalID: string;
  update: (next: React.FC) => void;
}
AVAILABLE IMMEDIATELY
promise01
Resolves when this entry closes.
modalID02
Targets this entry from another call site.
update03
Replaces the component while its ID, backdrop, stack position, and pending promise stay put.

UPDATE REMOUNTSupdate() starts a fresh component mount. Local React state resets.

CLOSE RESULTS

What HideReturn<T> records

A submitted answer, backdrop tap, swipe, Android back press, and hideAll() resolve differently. Only hide(data) returns a payload.

Read HideReturn<T>
Choose one close action
Choose an answer

Answer here, swipe down, or choose another close action.

HideReturn<Answer>promise pending
const result = await handle.promise;
// waiting for a close
Press one action to resolve the promise
GET STARTED

Add MagicModalPortal to your app root

The setup guide mounts the app-root portal and shows how to await a typed HideReturn<T>.

Set up the portal