Search code examples
javascriptreactjsthree.jsreact-springreact-three-fiber

How do I mount/unmount a component in React with a keystroke?


I'm making an app that renders 3d objects on keyboard press. I would like these objects to disappear after 2-3 seconds, or when the animation is finished playing.

This is a component that handles the key pressed logic:

const Selector = () => {
  const [selector, setSelector] = useState(null);
  useEffect(() => {
    function setupKeyLogger() {
      document.onkeydown = function (e) {
        console.log(e);
        switch (e.keyCode) {
          case 65:
            setSelector('SpinningCube');
            break;
          case 83:
            setSelector('SmallCube');
            break;
          default:
            break;
        }
      };
    }
    setupKeyLogger();
  });
  switch (selector) {
    case 'SpinningCube':
      return (
        <>
          <SpinningCube />
        </>
      );
    case 'SmallCube':
      return (
        <>
          <SmallCube />
        </>
      );
    default:
      return <></>;
  }
};

Right now, it's switching between the two objects <SpinningCube /> and <SmallCube /> when I press the two different keys. However if I spam a key, it only renders the component on the initial key press. I want it to re-render and restart the animation on the component every time I hit the key, even if it is the same one over and over. I'm assuming I need a way to unmount and then remount the component upon every key stroke?

I am using Three and React Spring to handle my animations.


Solution

  • Components will re-render when props or state have changed. When you spam a button, the same state gets set, but not changed, so nothing happens. So, what you need is another parameter to flip on each press and pass to the children (forcing them to re-render).

    const Selector = () => {
      const [selector, setSelector] = useState(null);
      const [forceRender, setForceRender] = useState(0);
      const forceAnimation = (selected) => {
        setSelector(selected);
        setForceRender(++forceRender);
      };
    
      useEffect(() => {
        function setupKeyLogger() {
          document.onkeydown = function (e) {
            console.log(e);
            switch (e.keyCode) {
              case 65:
                forceAnimation('SpinningCube');
                break;
              case 83:
                forceAnimation('SmallCube');
                break;
              default:
                break;
            }
          };
        }
        setupKeyLogger();
      });
    
      switch (selector) {
        case 'SpinningCube':
          return (
            <key={forceRender}> // if this doesn't work, I'd try passing it to a React.Fragment or div instead
              <SpinningCube />
            </>
          );
        case 'SmallCube':
          return (
            <key={forceRender}>
              <SmallCube />
            </>
          );
        default:
          return <></>;
      }
    };