@berish/portal

Library for creating global DOM elements and working with them as with React components

Usage no npm install needed!

<script type="module">
  import berishPortal from 'https://cdn.skypack.dev/@berish/portal';
</script>

README

@berish/portal

Library for creating global DOM elements and working with them as with React components

Installation

$ npm install @berish/portal --save

or

$ yarn add @berish/portal

Supports typescript

Get started

First you need to decide where the root of the portals will be located.

import { Root } from '@berish/portal';

class App extends React.Component {
  render() {
    return (
      ...
      <Root />
      ...
    );
  }
}

After that, you can render any element using the portals.

import { Portal } from '@berish/portal';

class TestDiv extends React.Component {
  render() {
    return <div>Hey bro</div>
  }
}

...
someAction() {
   Portal.create(TestDiv)();
}
...

With some response from portallable element

import { Portal } from '@berish/portal';

class TestForm extends React.Component {

  resolve = () => {
    this.props.portal.resolve({name: 'Ravil'})
  }

  reject = () => {
    // Close form with rejected reason
    this.props.portal.reject('My some reason')
  }

  render() {
    return (
      <div>
        <h1>Hey bro, click button</h1>
        <button onClick={this.resolve}>Resolve</button>
        <button onClick={this.reject}>Reject</button>
      </div>
    );
  }
}

...
async someAction() {
  try {
    const result = await Portal.create(TestForm)();
    console.log(result); // { name: 'Ravil' }
  } catch (err) {
    console.log(err); // My some reason
  }
}
...

Example with modals (real case):

import { Portal } from '@berish/portal';
import { Modal } from 'any-ui-kit'; // example

class TestModal extends React.Component {
  
  save = () => {
    this.props.portal.resolve(this.state.someData)
  }

  cancel = () => {
    this.props.portal.resolve();
  }

  render() {
    return (
      <Modal onClose={this.cancel} visible={true}>
        <h1>Hey bro, click button</h1>
        // {...}
        <button onClick={this.save}>Save</button>
        <button onClick={this.cancel}>Cancel</button>
      </Modal>
    );
  }
}

...
async someAction() {
  const result = await Portal.create(TestForm)();
  console.log(result); // if clicked at Cancel button - null. 
}

API

Root props:

interface IRootProps {
  portalName?: string;
  children?: React.ReactNode;
}

portalName - It is scope of Portal (defaults default). Not required. children - Any content that can be rendered on top of the portal (defaults undefined). Not required.

Portal API:

Portal.scope(portalName?: string); // Select root by portalName
const destroy = Portal.add(element: JSX.Element); // You can show native JSX element. Returns destroy function; 
Portal.update(); //Re-render all portal's elements
Portal.listen(cb: (jsxElements: JSX.Element[]) => void); // Listen for updates
const withPortal = Portal.create(component: React.ComponentClass | React.SFC); // HOC of portals
const promise = withPortal(props?: Props, elementId?: string); // Show element in Root, elementId - id for destroy this element manual;

withPortal.destroy(); // Destroy all elements, whos HOC'ed in withPortal
withPortal.destroy(elementId?: string); // Destroy only one element by ID;

promise.elementId; // Automatic generated or manual ID for destroy. You can do withPortal.destroy(promise.elementId);