alex-cory/react-useportal

Feature request: listener onOpen/onClose

Closed this issue ยท 15 comments

rijk commented

In some cases you want to handle open/closed state management from a parent component (for instance, if you want to close the modal after some data has been saved). I can open/close the portal via a prop by doing something like this in the component:

  const { openPortal, closePortal, isOpen, Portal } = usePortal({ isOpen: props.open })

  useEffect( () => {
    if ( props.open === false && isOpen ) closePortal()
    if ( props.open && !isOpen ) openPortal()
  }, [ props.open, isOpen ] )

And it would be nice to also have an onOpen/onClose to respond to closing the portal via ESC or clicking outside the modal.

Hope it's clear. Thanks!

oh interesting! I'll get to work asap on these :) And can you elaborate a little with an example for the onOpen/onClose

rijk commented

Sweet. The example is a modal where the state is fully managed from parent. Would a CodeSandbox help to clarify?

rijk commented

Here's an example of a managed modal, I tried to explain the use case as well: https://codesandbox.io/s/useportal-bu7j0

As you can see, closing it after saving works fine, but because there are no open/close events I cannot tap in to the built-in functionality of closing on ESC/clicking outside the modal.

Awesome, I will take a look at this tomorrow! :) Sorry for the delay, a lot going on at work currently

Okay, so after looking at this again with a clear head, what's stopping you from using the "stateless" Portal here? https://codesandbox.io/s/useportal-managed-state-j5m1x

I'm trying to see if what you're asking works for the onClose/onOpen/isOpen. Still not done with solution, but will keep going after work

Okay, so I don't think this can actually be done the way you want. I might be able to get it so there could be something like

const { onPortalClose, onEsc, onOutsideClick } = usePortal()

onPortalClose(() => {
  // run whatever is passed in
})

but I haven't figured out if that's^ possible yet.

The problem is when you do it this way

const { closePortal } = usePortal({ onClose, isOpen })

is that, unlike components, hooks don't "rerender with updated arguments" like components do with props. Does this make sense?

rijk commented

Hi Alex, sorry for the late reply, was hiking in the Dolomites ๐Ÿ”

The managed state example at https://codesandbox.io/s/useportal-managed-state-j5m1x doesn't work for the same reason as mine: the built-in ESC and outside click functionality is missing.

What do you think about this API?

<Portal onOpen={...} onClose={...}>
  ...
</Portal>

Would that solve the problem?

Hmm... so I thought about this previously, but how would you get the props inside the <Portal /> from inside the usePortal hook. I need to think about this some more

rijk commented

At the risk of it being not relevant, maybe this hook can be of inspiration? https://github.com/wsmd/react-use-form-state/blob/master/src/useFormState.js

rijk commented

Another idea that could maybe inspire, here's an approach I used for one of my functional components to be able to control open/closed state from the outside. Use case is a popover menu; the component also contains the trigger and so normally it controls its own state, but in some cases we want to keep it open on clicks, and close it manually โ€” for example a date picker where you don't want to close it on month navigation, but do want to close after a user has selected a date and the change was saved:

Screenshot 2019-06-16 at 15 33 41

In the code implementing it I can do <Menu ref={ref => this.menu = ref}>, and then this:

handleDate( date ) {
  this.props.onDate( date )
  this.menu.close()
}

No idea if this is a valid use of React refs... ๐Ÿ™ˆ but it works.

if this is a valid use of React

No, to create something like on your shcreenshot you need useImperativeHandle:
React Docs

rijk commented

@infodusha Thanks for the pointer!

I'm going to play around with the onClose and onOpen soon. I just haven't had time yet. I'm sure there's a way to implement it

This feature has been implemented :)