简体   繁体   中英

React Modal and Tooltip issue when using Portal

I have a simple Modal component and a simple Tooltip component. Both of them can be opened by clicking on the triggering button and dismissed by clicking outside. To detect clicking outside, I use this simple hook:

const useClickAway = (
  ref: Ref,
  condition: boolean,
  handler: Handler
): void => {

  useEffect(() => {
    const listener = (e: Event) => {
      if (!ref.current || ref.current.contains(e.target as Node)) {
        return;
      }
      handler(e);
    };
    if (condition) {
      document.addEventListener('mouseup', listener);
      document.addEventListener('touchend', listener);
    }
    return () => {
        document.removeEventListener('mouseup', listener);
        document.removeEventListener('touchend', listener);
    };
  }, [ref, handler, condition]);
};

And this is how I use it:

/*
* ref - reference to the modal container
* isOpen - The modal state.
* handleClose - Handler that closes the modal.
*/
useClickAway(ref, isOpen, handleClose)

It has been working fine so far, but the issue appeared when I try to render my tooltip (which uses Portal to render it into the body element, instead of the react tree) inside this Modal.

When I open the modal and then open the tooltip inside it, clicking on the tooltip is causing the modal to close. Because clicking on the tooltip is considered as clicking outside for the Modal.

Can anyone provide clean solution to this problem?

I believe you can take advantage of forwardRef to pass a ref that is defined in the Modal Tooltip shared parent.

Here is how I would do it:

First, I would re-write both Tooltip and Modal to accept an optional external prop ref . such as:

const Tooltip = React.forwardRef((props, ref) => {
  const tooltipLocalRef = useRef(null);
  const tooltipRef = ref || tooltipLocalRef;

  //
});

// usage:
const tooltipRef = useRef(null);
<Tooltip ref={tooltipRef} anotherProps={someValue} />

Same goes for Modal component, but, in addition to the ref that we'll use for the Modal itself, we'll also send the tooltipRef as an extra prop.


const tooltipRef = useRef(null);
const modalRef = useRef(null);


<Tooltip ref={tooltipRef} anotherProp={someValue} />
<Modal ref={modalRef} tooltipRef={tooltipRef} anotherProp={someValue} />

By doing that, I believe we can check against the click outside the modal and make an exception for when that target is within the tooltipRef.current Node.

An extra work on the modal handleClose handler:

function handleClose(e) {
      if (!props.tooltipRef.current || props.tooltipRef.current.contains(e.target as Node)) {
        return;
      }
      setModalOpen(false)
}

I haven't tested that, let me know how it turns out.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM