Home > Back-end >  How to prevent child with `position: absolute` from triggering `onBlur` event of parent?
How to prevent child with `position: absolute` from triggering `onBlur` event of parent?

Time:03-19

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>
  );
}

CodePudding user response:

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.

CodePudding user response:

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>

CodePudding user response:

I am not sure if this is what you were thinking so that when you click the outer div we set active to false ? and then if we click the block we dont want it to dissapeaer

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

export default function App() {
  const [isActive, setIsActive] = React.useState(false);
  const ref = React.useRef(null);
  const focus = (e) => {
    ref.current.focus();
  };
  const handleClick = (e) => {
    e.preventDefault();
    if (document.activeElement !== ref.current) {
      setIsActive(false);
    }
  };

  return (
    <div className="container" onClick={handleClick}>
      <div className="container-inner">
        <input ref={ref} id="input" onFocus={() => setIsActive(true)} />
        {isActive && <div className="child" onClick={focus} />}
      </div>
    </div>
  );
}
  • Related