Promise portals for React
Using npm:
$ npm install react-promiseportal
Using yarn:
$ yarn add react-promiseportal
Then with a module bundler like webpack, use as you would anything else:
// Using ES6 Modules
import { usePromisePortal } from 'react-promiseportal';
// using CommonJS modules
const usePromisePortal = require('react-promiseportal').usePromisePortal;Normally when managing a modal, or similar hierarchy agnostic elements, it is common practice to render these in the component, and control them with local boolean state.
For example:
// App.js
import React from 'react';
function App() {
  const [isOpen, setOpen] = React.useState(false);
  return (
    <>
      <button onClick={() => setOpen(true)}>Click me to open a modal</button>
      <SomeModalComponent open={isOpen}>
        <button
          onClick={() => {
            alert('You confirmed!');
            setOpen(false);
          }}
        >
          Confirm
        </button>
        <button
          onClick={() => {
            alert('You cancelled.');
            setOpen(false);
          }}
        >
          Cancel
        </button>
      </SomeModalComponent>
    </>
  );
}In large components, managing several modals with different isOpen state can be confusing.
With react-promiseportal, I offer complete co-location.
At the top-level of your application, import the PromisePortalProvider module.
// AppContext.js
import React from 'react';
import { PromisePortalProvider } from 'react-promiseportal';
function AppContext() {
  return (
    <PromisePortalProvider>
      <App />
    </PromisePortalProvider>
  );
}You can then import usePromisePortal (hook) or withPromisePortal (hoc).
// App.js
import React from 'react';
import { usePromisePortal } from 'react-promiseportal';
function App() {
  const portal = usePromisePortal();
  return (
    <button
      onClick={async () => {
        const didConfirm = await portal((onConfirm, onCancel) => {
          return (
            <SomeModalComponent open>
              <button onClick={onConfirm}>Confirm</button>
              <button onClick={onCancel}>Cancel</button>
            </SomeModalComponent>
          );
        });
        if (didConfirm) alert('You confirmed!');
        else alert('You cancelled.');
      }}
    >
      Click me to open a modal
    </button>
  );
}Calling onConfirm with an Event or a falsy value, will resolve the promise with the value true.
If called with a truthy value, like an object, this is returned instead.
For example, if the Modal were instead a form, which returned some input for a request:
// App.js
import React from 'react';
import { usePromisePortal } from 'react-promiseportal';
function App() {
  const portal = usePromisePortal();
  return (
    <button
      onClick={async () => {
        // onConfirm is called with {potatoes: true}
        // So the promise is resolved with {potatoes: true}
        const input = await portal((onConfirm, onCancel) => {
          return (
            <SomeFormComponent open>
              <button onClick={() => onConfirm({ potatoes: true })}>
                Confirm
              </button>
              <button onClick={onCancel}>Cancel</button>
            </SomeFormComponent>
          );
        });
        if (input) fetch();
        else alert('You cancelled.');
      }}
    >
      Click me to open a modal
    </button>
  );
}react-promiseportal is built and maintained by babangsund.
@blog.
@github.
@twitter.