Why is the blur event listener fired twice for the same element with contenteditable in this case?

40 Views Asked by At

This is the code, which I reduced and simplified as much as I could:

import { useEffect, useRef, useState } from 'react';

export default function App() {
  const parentElementRef = useRef();
  const childElementRef = useRef();
  const [focusedElement, setFocusedElement] = useState();

  useEffect(() => {
    const clickEventListener = (event) => {
      event.stopPropagation();
      setFocusedElement(event.currentTarget);
      console.log('click');
      console.log(event.target === childElementRef.current);
      console.log(event.currentTarget === childElementRef.current);
    };

    const blurEventListener = (event) => {
      event.stopPropagation();
      console.log('blur');
      console.log(event.target === childElementRef.current);
      console.log(event.currentTarget === childElementRef.current);
    };

    parentElementRef.current.addEventListener('click', clickEventListener);
    parentElementRef.current.addEventListener('blur', blurEventListener);

    childElementRef.current.addEventListener('click', clickEventListener);
    childElementRef.current.addEventListener('blur', blurEventListener);

    return () => {
      childElementRef.current.removeEventListener('click', clickEventListener);
      childElementRef.current.removeEventListener('blur', blurEventListener);

      parentElementRef.current.removeEventListener('click', clickEventListener);
      parentElementRef.current.removeEventListener('blur', blurEventListener);
    };
  }, []);

  useEffect(() => {
    focusedElement?.focus();
  }, [focusedElement]);

  return (
    <div
      ref={parentElementRef}
      style={{
        display: 'flex',
        flexDirection: 'column',
        width: '100vw',
        height: '100vh',
        backgroundColor: 'green',
      }}
    >
      <div
        ref={childElementRef}
        contentEditable={focusedElement === childElementRef.current}
        suppressContentEditableWarning
        style={{
          width: '200px',
          height: '200px',
          backgroundColor: 'red',
        }}
      >
        Hello world!
      </div>
    </div>
  );
}

And this is a StackBlitz link to try it out:

https://stackblitz.com/edit/vitejs-vite-pibe1s?file=src%2FApp.jsx

If you click on the 'Hello world!' text and then on the green space, you will get this output:

click
true
true
blur
true
true
click
false
false
blur
true
true

If you click on the red square, BUT NOT in the 'Hello world!' text, you get this output:

click
true
true
blur
true
true
click
false
false

If you remove display: 'flex' from the parent element, you will always get the same output no matter where you click on the red box.

I just want to understand this behavior. Why does it even matter if I have display: 'flex' or not in the parent, and when it has it, why does clicking in the text make any difference in how many times the blur event is executed?


Desired behavior:

The code should output this in the console when interacted as specified, which is what I would expect:

click
true
true
blur
true
true
click
false
false

Specific problem or error:

I get this output instead and I'd like to understand why:

click
true
true
blur
true
true
click
false
false
blur
true
true

Shortest code necessary to reproduce the problem:

This is the code I just posted at the beginning of this post.

0

There are 0 best solutions below