Code Monkey home page Code Monkey logo

react-cool-portal's Introduction

REACT COOL PORTAL

This is a React hook for Portals. It helps you render children into a DOM node that exists outside the DOM hierarchy of the parent component. From now on you will never need to struggle with modals, dropdowns, tooltips etc. Check the features section out to learn more. Hope you guys ๐Ÿ‘๐Ÿป it.

โค๏ธ it? โญ๏ธ it on GitHub or Tweet about it.

build status coverage status npm version npm downloads npm downloads gzip size All Contributors PRs welcome Twitter URL

Live Demo

demo

โšก๏ธ Try yourself: https://react-cool-portal.netlify.app

Features

  • ๐Ÿ’ Renders an element or component to <body> or a specified DOM element.
  • ๐ŸŽฃ React Portals feat. Hook.
  • ๐Ÿค– Built-in state controllers, event listeners and many useful features for a comprehensive DX.
  • ๐Ÿงฑ Used as a scaffold to build your customized hook.
  • ๐Ÿงน Auto removes the un-used portal container for you. Doesn't produce any DOM mess.
  • ๐Ÿ“œ Supports TypeScript type definition.
  • ๐Ÿ—„๏ธ Server-side rendering compatibility.
  • ๐Ÿฆ” Tiny size (~ 1KB gzipped). No external dependencies, aside for the react and react-dom.

Requirement

To use react-cool-portal, you must use [email protected] or greater which includes hooks.

Installation

This package is distributed via npm.

$ yarn add react-cool-portal
# or
$ npm install --save react-cool-portal

Usage

Here are some minimal examples of how does it work. You can learn more about it by checking the API out.

Basic Use Case

Inserts an element or component into a different location in the DOM.

import usePortal from "react-cool-portal";

const App = () => {
  const { Portal } = usePortal();

  return (
    <div>
      <Portal>
        <p>
          Wow! I am rendered outside the DOM hierarchy of my parent component.
        </p>
      </Portal>
    </div>
  );
};

By default, the children of portal is rendered into <div id="react-cool-portal"> of <body>. You can specify the DOM element you want through the containerId option.

import usePortal from "react-cool-portal";

const App = () => {
  const { Portal } = usePortal({ containerId: "my-portal-root" });

  return (
    <div>
      <Portal>
        <p>Now I am rendered into the specify element (id="my-portal-root").</p>
      </Portal>
    </div>
  );
};

Note: If the container element doesn't exist, we will create it for you.

Use with State

react-cool-portal provides many useful features, which enable you to build a component with state. For instance, modal, dropdown, tooltip, and so on.

Edit usePortal

import usePortal from "react-cool-portal";

const App = () => {
  const { Portal, isShow, show, hide, toggle } = usePortal({
    defaultShow: false, // The default visibility of portal, default is true
    onShow: (e) => {
      // Triggered when portal is shown
      // The event object will be the parameter of `show(e?)`
    },
    onHide: (e) => {
      // Triggered when portal is hidden
      // The event object will be the parameter of `hide(e?)`, it maybe MouseEvent (on clicks outside) or KeyboardEvent (press ESC key)
    },
  });

  return (
    <div>
      <button onClick={show}>Open Modal</button>
      <button onClick={hide}>Close Modal</button>
      <button onClick={toggle}>{isShow ? "Close" : "Open"} Modal</button>
      <Portal>
        <div className="modal" tabIndex={-1}>
          <div
            className="modal-dialog"
            role="dialog"
            aria-labelledby="modal-label"
            aria-modal="true"
          >
            <div className="modal-header">
              <h5 id="modal-label" className="modal-title">
                Modal title
              </h5>
            </div>
            <div className="modal-body">
              <p>Modal body text goes here.</p>
            </div>
          </div>
        </div>
      </Portal>
    </div>
  );
};

๐Ÿงน When no element in the container, we will remove it for you to avoid DOM mess. However, the feature can be turn off via the autoRemoveContainer option.

The above example shows how easy you can handle the visibility of your component. You may ask how to handle the visibility with animations? No worries, you can disable the built-in show/hide functions by setting the internalShowHide option to false then handling the visibility of your component via the isShow state.

Edit usePortal with Animation

import usePortal from "react-cool-portal";

const App = () => {
  const { Portal, isShow, show, hide, toggle } = usePortal({
    defaultShow: false,
    internalShowHide: false, // Disable the built-in show/hide portal functions, default is true
    onShow: (e) => {
      // Triggered when `isShow` is set to true
    },
    onHide: (e) => {
      // Triggered when `isShow` is set to false
    },
  });

  return (
    <div>
      <button onClick={show}>Open Modal</button>
      <button onClick={hide}>Close Modal</button>
      <button onClick={toggle}>{isShow ? "Close" : "Open"} Modal</button>
      <Portal>
        <div
          // Now you can use the `isShow` state to handle the CSS animations
          className={`modal${isShow ? " modal-open" : ""}`}
          tabIndex={-1}
        >
          <div
            className="modal-dialog"
            role="dialog"
            aria-labelledby="modal-label"
            aria-modal="true"
          >
            <div className="modal-header">
              <h5 id="modal-label" className="modal-title">
                Modal title
              </h5>
            </div>
            <div className="modal-body">
              <p>Modal body text goes here.</p>
            </div>
          </div>
        </div>
      </Portal>
    </div>
  );
};

Besides that, you can also handle the visibility of your component via React animation events or transition events like what I did for the demo app.

Build Your Customized Hook

Are you tired to write the same code over and over again? It's time to build your own hook based on react-cool-portal then use it wherever you want.

import { useCallback } from "react";
import usePortal from "react-cool-portal";

// Customize your hook based on react-cool-portal
const useModal = (options = {}) => {
  const { Portal, isShow, ...rest } = usePortal({
    ...options,
    defaultShow: false,
    internalShowHide: false,
  });

  const Modal = useCallback(
    ({ children }) => (
      <Portal>
        <div className={`modal${isShow ? " modal-open" : ""}`} tabIndex={-1}>
          {children}
        </div>
      </Portal>
    ),
    [isShow]
  );

  return { Modal, isShow, ...rest };
};

// Use it wherever you want
const App = () => {
  const { Modal, show, hide } = useModal();

  return (
    <div>
      <button onClick={show}>Open Modal</button>
      <button onClick={hide}>Close Modal</button>
      <Modal>
        <div
          className="modal-dialog"
          role="dialog"
          aria-labelledby="modal-label"
          aria-modal="true"
        >
          <div className="modal-header">
            <h5 id="modal-label" className="modal-title">
              Modal title
            </h5>
          </div>
          <div className="modal-body">
            <p>Modal body text goes here.</p>
          </div>
        </div>
      </Modal>
    </div>
  );
};

One problem of the above example is that CSS transition/animation will be cut off due to the re-creating of the Portal component. So if you want to apply transitions or animations to the wrapped element of the customized hook. The isShow need to be passed from the props.

Edit usePortal - custom

const useModal = (options = {}) => {
  const { Portal, ...rest } = usePortal({
    ...options,
    defaultShow: false,
    internalShowHide: false,
  });

  const Modal = useCallback(
    // Pass the `isShow` from props to prevent CSS transition/animation to be cut off
    ({ isShow, children }) => (
      <Portal>
        <div className={`modal${isShow ? " modal-open" : ""}`} tabIndex={-1}>
          {children}
        </div>
      </Portal>
    ),
    []
  );

  return { Modal, ...rest };
};

Conditionally ESC or Click Outside to Hide

ESC or click outside to hide is an out-of-box feature of react-cool-portal. However, you can conditionally hide the portal based on the presence or absence of an element. Here we take a nested modal as the example:

import usePortal from "react-cool-portal";

const App = () => {
  const [showChildModal, setShowChildModal] = useState(false);
  const { Portal: ChildModal, show } = usePortal({ defaultShow: false });
  const { Portal: ParentModal } = usePortal({
    // Provide the class name of the child modal, so the parent modal will only be hidden after the child modal is hidden
    escToHide: ["child-modal"],
    // The same as above
    clickOutsideToHide: ["child-modal"],
  });

  return (
    <div>
      <ParentModal>
        <div>
          <p>I'm parent modal.</p>
          <button onClick={show}>Open Child Modal</button>
          <ChildModal>
            <div className="child-modal">
              <p>I'm child modal.</p>
            </div>
          </ChildModal>
        </div>
      </ParentModal>
    </div>
  );
};

API

const returnObj = usePortal(parameterObj);

Return object

It's returned with the following properties.

Key Type Default Description
Portal component Renders children into a DOM node that exists outside the DOM hierarchy of the parent component.
isShow boolean false The show/hide state of portal.
show function To show the portal or set the isShow to true.
hide function To hide the portal or set the isShow to false.
toggle function To toggle (show/hide) the portal or set the isShow to true/false.

Parameter object (optional)

When use react-cool-portal you can configure the following options via the parameter.

Key Type Default Description
containerId string react-cool-portal You can specify your own container id from an existing DOM element or let this hook automatically creates it for you.
autoRemoveContainer boolean true Enable/disable the built-in automatically remove container function.
defaultShow boolean true The initial show/hide state of the portal.
clickOutsideToHide boolean | array true Hide the portal by clicking outside of it. You can also provide class name(s) for conditionally hide.
escToHide boolean | array true Hide the portal by pressing ESC key. You can also provide class name(s) for conditionally hide.
internalShowHide boolean true Enable/disable the built-in show/hide portal functions, which gives you a flexible way to handle your portal.
onShow function Triggered when portal is shown or the isShow set to true.
onHide function Triggered when portal is hidden or the isShow set to false.

Articles / Blog Posts

๐Ÿ’ก If you have written any blog post or article about react-cool-portal, please open a PR to add it here.

Contributors โœจ

Thanks goes to these wonderful people (emoji key):


Welly

๐Ÿ’ป ๐Ÿ“– ๐Ÿšง

Dawid Karabin

๐Ÿ“–

Honza Stepanovsky

๐Ÿ›

This project follows the all-contributors specification. Contributions of any kind welcome!

react-cool-portal's People

Contributors

allcontributors[bot] avatar dependabot-preview[bot] avatar dependabot[bot] avatar hhhonzik avatar hinok avatar wellyshen avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

react-cool-portal's Issues

Animation not working while creating a custom hook

Bug Report

Describe the Bug

I followed the doc to create a custom hook, but I feel like the transition CSS property is not working

How to Reproduce

Here is a sandbox that reproduces the issue, I just copy/paste the code from the doc

Expected Behavior

The show/hide functionality is working fine, it just missing the transition

Your Environment

  • OS: macOS
  • Browser Chrome
  • Version of react-cool-portal: 1.0.14

Documentation: tooltip/dropdown example

I've had a good look through the documentation - but I'm struggling to see how to best implement a tooltip/dropdown type feature where the contents of the portal need to be updated with position information from the click target.

Would it be possible to include some pointers in the documentation?

Thanks so much!

Swapping content in the portal closes it

Bug Report

Describe the Bug

When you swap content in a portal, the portal closes.

How to Reproduce

  • Create a portal with a button in it. This button should be replaced when another content when clicked.
  • Click on the button, the portal closes.

CodeSandbox Link

https://codesandbox.io/s/focused-wing-xsyqq

Expected Behavior

The portal should stay opened.

Your Environment

  • OS: macOS
  • Browser: Chrome
  • Version of react-cool-portal: v1.0.20

Additional Information

I tracked down the issue, the problem is here. When this code is executed, the content is already replaced so the condition returns true.
I found a workaround with a setTimeout (shown in the CodeSandbox) but I think this should be solved in the library side.

Empty default container appears on init

Bug Report

Describe the Bug

When I render component with this hook, I see in the DOM empty container. When I try to show it and then hide, container disappear as expected. The problem occurs only on init. I have initialised that hook with a defaultShow: false.

How to Reproduce

  1. Go to https://codesandbox.io/s/kind-ride-6ux23?file=/src/App.js
  2. Open devtools and inspect #react-cool-portal
  3. See that container exists on init.

Expected Behavior

Container should not be visible on init, when I set defaultShow: false

Screenshots

Add screenshots to help explain your problem.

Your Environment

  • OS: macOs
  • Browser: Chrome
  • Version of react-cool-portal: v1.0.11

Dont delete container, or provide a means to retain it

I have a container that I use with containerId. However when the portal closes you delete the container element. This breaks my app.

The reason I use a custom element is to apply a className to the container to have the portal content styled.

After closing the portal, your library deletes this container element, then when re-opening the portal it re-creates the element under the body which is in the wrong place

`useModal` hook from the README not working

I'm trying to create a custom hook useModal as in the README.
But I think there is a problem in the dependencies from the useCallback hook because it's not working.

const useModal = (options = {}) => {
  const { Portal, isShow, ...rest } = usePortal({
    ...options,
    defaultShow: false,
  });

  const Modal = useCallback(
    ({ children }) => (
      <Portal>
        <div class={`modal${isShow ? " modal-open" : ""}`} tabIndex={-1}>
          {children}
        </div>
      </Portal>
    ),
    [] // <- here
  );

  return { Modal, isShow, ...rest };
};

When I call show, the modal doesn't open.

I think we could put isShow in the dependency list to fix the problem.

Unexpected side-effect when providing containerId

Bug Report

Describe the Bug

The containerId parameter would create a new element with this id unless the container already exists. This is not a documented behaviour and I would expect an error in this case.

The docs just say this:

You can specify the container of portal you want by setting it as the id of the DOM element.

How to Reproduce

  1. Provide a containerId that doesn't exist on the page to the usePortal hook.
  2. Find a new element in the DOM with this containerId.

Expected Behavior

Error raised or nothing.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.