alex-cory/react-useportal

Trigger openPortal or any action without event

Sebastp opened this issue ยท 19 comments

I'm trying to openPortal on route change.
When route changes, I get;
You must either add a ref to the element you are interacting with or pass an event to openPortal(e) or togglePortal(e).

How do I do it without ref or event?

This is a good point. The way you are trying to use the library I haven't seen before. Every way that I've thought to use it was by interacting with something. i.e. click a button, have a modal show, hover an element, have a tooltip show. As a workaround until I get this changed, you could add a ref to a random element.

const App = () => {
  const { ref, Portal } = usePortal()
  return (
    <>
      <div style={{ display: 'none' }} ref={ref} />
      <Portal />
    </>
  )
}

That should work until I get this changed

Nope, it does not.
Still getting the same error

I will take a look at this asap. Swamped with work right now :/

Any news on that?
I get this when using your way;
Untitled-1

Still getting "Error: You must either add a ref to the element you are interacting with or pass an event to openPortal(e) or togglePortal(e)."
Does anyone have a workaround?

I have a similar use case where a modal can be opened programmatically without user interaction when an API returns an error response. It would be great to support this when you get a chance @alex-cory.

In the meantime @Sebastp I am using something like this as a workaround:

import React, { useEffect } from 'react';
import usePortal from 'react-useportal';

// Passing this to openPortal tricks it into thinking it was from a user
const NULL_EVENT = { currentTarget: true };

const Modal = () => {
  const { openPortal, closePortal, isOpen, Portal } = usePortal();

  useEffect(() => {
    openPortal(NULL_EVENT);
  }, []);

  return (
    isOpen && (
      <Portal>
        <div>
          This will open immediately
       	  <button onClick={closePortal}>close</button>
		</div>
      </Portal>
    )
  );
};

I have a similar use case where a modal can be opened programmatically without user interaction when an API returns an error response. It would be great to support this when you get a chance @alex-cory.

In the meantime @Sebastp I am using something like this as a workaround:

import React, { useEffect } from 'react';
import usePortal from 'react-useportal';

// Passing this to openPortal tricks it into thinking it was from a user
const NULL_EVENT = { currentTarget: true };

const Modal = () => {
  const { openPortal, closePortal, isOpen, Portal } = usePortal();

  useEffect(() => {
    openPortal(NULL_EVENT);
  }, []);

  return (
    isOpen && (
      <Portal>
        <div>
          This will open immediately
       	  <button onClick={closePortal}>close</button>
		</div>
      </Portal>
    )
  );
};

Thanks man <3 I'll try it out

I will get to this as soon as I can. Apologies for it taking so long.

+1

import React, { useEffect } from 'react';
import usePortal from 'react-useportal';

// Passing this to openPortal tricks it into thinking it was from a user
const NULL_EVENT = { currentTarget: true };

const Modal = () => {
  const { openPortal, closePortal, isOpen, Portal } = usePortal();

  useEffect(() => {
    openPortal(NULL_EVENT);
  }, []);

  return (
    isOpen && (
      <Portal>
        <div>
          This will open immediately
       	  <button onClick={closePortal}>close</button>
		</div>
      </Portal>
    )
  );
};

This works in an SSR scenario but then there seems to be issues when i try and interact with any element inside the portal (which is a modal in my implementation) as i get the following error:

Uncaught TypeError: target.current.contains is not a function
    at containsTarget (usePortal.js:114)
    at usePortal.js:115
    at HTMLDocument.<anonymous> (usePortal.js:126)

which points to :

    .......
    var handleOutsideMouseClick = react_1.useCallback(function (e) {
        var containsTarget = function (target) { return target.current.contains(e.target); };
     ......

Since interacting with the modal is crucial in my app currently, directly editing the node_modules dist file with var containsTarget = function (target) { return target.current && target.current.contains(e.target) || null; }; seems to make it work alright.

I guess its not recommended at all but it is allowing me to continue on with the rest of the app development for now. Not sure if this fix could effect other functionalities and use-cases of this package though it does seem a rather innocuous change.

bisak commented

I too have cases where I have to open modals programatically w/o any buttons and I'm using all kinds of nasty workarounds. Any input on this issue is greatly appreciated.

If the NULL_EVENT workaround suggested by @talbet-qga actually works, is there ever any need to pass an actual event or ref? Or is there some situation where it would still fail?

I have my own useModal() wrapper around usePortal() and I was thinking if I should just change it to always use a NULL_EVENT. I've been using empty <div ref={ref} /> elements in my components only for opening modals which is really clumsy.

bisak commented

NULL_EVENT does not work in my experience. I've also been using empty divs and what not, but definitely not optimal.

I only have experience of one case, where a modal that was set up in a custom hook was supposed to be opened in a parent component's useEffect() hook when certain conditions applied. In that case, I wasn't able to get it working with ref, and there obviously was no user-initiated event, but the NULL_EVENT trick worked.

It turns out NULL_EVENT isn't foolproof, but to avoid Uncaught TypeError: target.current.contains is not a function you could try this:

const NULL_EVENT = { currentTarget: { contains: () => false } };

@alex-cory have you had a chance to look at this? Would you accept a PR?

Apologies, I have not had a chance to take a look. Yes, I would absolutely accept a PR!

I've submitted a PR to fix this: #83.