2

I have a container that has two children: an input field and another div element that conditionally renders depending on the value for isActive. The problem that I'm running into is that whenever I click on the child div, then onBlur is triggered.

How can I prevent this from happening? I've already tried e.stopPropagation() as you can see below. Also, I've tried moving onBlur and onFocus to the container div, but that didn't work either.

pensive-forest-ksmi2o

import React from "react";
import "./styles.css";

export default function App() {
  const [isActive, setIsActive] = React.useState(false);

  const handleClick = React.useCallback((e) => {
    e.stopPropagation(); // does not work
  }, []);

  return (
    <div
      className="container"
      onFocus={() => setIsActive(true)}
      onBlur={() => setIsActive(false)}
    >
      <input />
      {isActive && <div className="child" onClick={handleClick} />}
    </div>
  );
}
Mike K
  • 7,621
  • 14
  • 60
  • 120
  • This example is the best solution to the problem [Detect click outside React component](https://stackoverflow.com/questions/32553158/detect-click-outside-react-component) – DoubleM Oct 01 '22 at 18:08

2 Answers2

2

The problem is the onBlur handler on the input element is triggered every time the user clicks outside it. So using onBlur event listener may not be the solution.

If you are only interested in keeping the child div visible when the user clicks it, I propose to add a click event listener to the document, which when called, check whether the event target is inside the container div(parent of the input and child div) and hide the child div accordingly.

export default function App() {
  const [isActive, setIsActive] = useState(false);
  const container = useRef();

  useEffect(() => {
    const handler = (event) => {
      if (!container.current.contains(event.target)) {
        setIsActive(false);
      }
    };
    document.addEventListener("click", handler);
    return () => {
      document.removeEventListener("click", handler);
    };
  });

  return (
    <div className="container" ref={ctn}>
      <input className="input" onFocus={() => setIsActive(true)} />
      {isActive && <div className="child" />}
    </div>
  );
}

Edit hungry-rain-qmt75l

This solution will not work if you really care about the focus state of the input element. The user can move focus with tab.

hangindev.com
  • 4,573
  • 12
  • 28
0

You can try the fix here

Explanation

{isActive && <div className="child" onClick={handleClick} />}

handleClick here does not help for you, because it never gets triggered due to re-rendering, so that's why e.stopPropagation() is not working for your case.

Another problem is onFocus and onBlur are only applied for activable element, so it means these events happen on input field (not div). You can try to replace input with another div for the experiment.

So how to make div become activable element? Well, you just simply put tabindex for div. Your code will work properly

The tabindex global attribute indicates that its element can be focused

<div
      className="container"
      tabindex="100" //add tabindex here
      onFocus={() => setIsActive(true)}
      onBlur={(e) => {
        const currentTarget = e.currentTarget;

        // Give browser time to focus the next element
        requestAnimationFrame(() => {
          // Check if the new focused element is a child of the original container
          if (!currentTarget.contains(document.activeElement)) {
            setIsActive(false);
          }
        });
      }}
    >
      <input />
      {isActive && <div className="child" />}
</div>
Nick Vu
  • 14,512
  • 4
  • 21
  • 31
  • Thank you for this! I tried about a dozen solutions but none worked with my absolute-positioned element (a floating menu). This does. – DukeSilver Jun 10 '23 at 20:35