1

I put the full code here on CodesandBox. Click to check the real behavior now

expected behavior: after users right-click a new position on the area, the Transition effect will remount at the new position until the old Transition unmount fully.

real behavior now: unmount immediately when users right-click, though behavior still make sense as for UX, but I'm still curious how to achieve the effect like Mac OS behave.

// Main code snippet

import React, { FC, ReactNode, useRef, useState } from "react";
import { SwitchTransition, CSSTransition } from "react-transition-group";
import "./styles.css";
type Props = {
  children: ReactNode;
};
export const ContextMenuContainer = ({ children }: Props) => {
  const [popperMenuOpen, setPopperMenuOpen] = useState(false);
  const [anchor, setAnchor] = useState<{ x?: number; y?: number } | null>();

  const [key, setKey] = useState(0); // added state key to force remount of CSSTransition
  const nodeRef = useRef(null);

  const handlePopperMenuShow = () => {
    setPopperMenuOpen(true);
  };

  const handlePopperMenuClose = () => {
    setPopperMenuOpen(false);
  };

  const countAnchorRef = (event: any) => {
    const localX = event.pageX - event?.currentTarget?.offsetLeft;
    const localY = event.pageY - event?.currentTarget?.offsetTop;
    setAnchor({ x: localX, y: localY });
  };

  const handleContextMenu = (
    e: React.MouseEvent<HTMLDivElement, MouseEvent>
  ) => {
    handlePopperMenuClose();
    e.preventDefault();
    countAnchorRef(e);
    handlePopperMenuClose(); // close previous popper menu
    setKey((prevKey) => prevKey + 1); // update key to force remount of CSSTransition
    setTimeout(() => {
      handlePopperMenuShow(); // show new popper menu after previous menu is closed
    }, 100); // wait for previous menu to close before showing new menu
  };

  return (
    <div
      style={{
        position: "relative",
        height: "100px",
        border: "1px dashed black"
      }}
      onContextMenu={handleContextMenu}
      onMouseDown={(e) => {
        if (e.button === 0) {
          handlePopperMenuClose();
          // setAnchor(null);
        }
      }}
    >
      <CSSTransition
        // nodeRef={nodeRef}
        key={key}
        timeout={600}
        in={popperMenuOpen}
        mountOnEnter
        unmountOnExit
        classNames="message"
      >
        <div
          ref={nodeRef}
          onTransitionEnd={(e: React.TransitionEvent<HTMLDivElement>) => {
            handlePopperMenuClose();
            // setAnchor(null);
          }}
          style={{
            position: "absolute",
            top: anchor?.y ?? 0,
            left: anchor?.x ?? 0,
            zIndex: 10,
            backgroundColor: "rgba(25,25,25, 0.3)"
          }}
        >
          test
        </div>
      </CSSTransition>

      <div
        style={{
          width: "30px",
          height: "30px",
          backgroundColor: "rgba(100,100,100,0.12"
        }}
      ></div>

      {children}
    </div>
  );
};

1 Answers1

0

I figure out a way that prevents me from getting the desired behavior.

  1. setKey((prevKey) => prevKey + 1) will trigger re-render for the sake of the key props on CSSTransition. --> I need to remove first

  2. but after I remove the key prop, the behavior is still weird. Reason:

    Because my handleContextMenu function executes handlePopperMenuClose() first,
    the fade-put transition is not fully executed and then the countAnchorRef(e) is executed immediately, which results in the page re-render again.

  3. in order to solve the 2. issue, I need to set aside this dependency. All I need is to add one more state to know whether my animation is done or not.

And I need one more ref to keep track of the new X-Y position, which will not results in re-render(useState will results in re-render)

¡voilà

I find the answer myself and update the attached codesandbox