Ad

Disappearing DOM Element Does Not Register Click Event

- 1 answer

I'm constructing an input field in React that looks like so:

Search box

When the 'x' is clicked (StyledCloseCircle), the text will be cleared, and the 'x' symbol should disappear. The 'x' symbol is currently shown with javascript when the input field is focused,

export const Search = React.forwardRef((props, ref) => {
  const [isFocused, setFocus] = useState(false);
  const [isHovered, setHover] = useState(false);

  return (
    <InputContainer
      onMouseEnter={() => setHover(true)}
      onMouseLeave={() => setHover(false)}
    >
      <StyledInput
        onFocus={() => setFocus(true)}
        onBlur={() => setFocus(false)}
        isHovered={isHovered}
        ref={ref}
        {...props}
      />
      {isFocused && !props.value && (
        <StyledMagnifyingGlass
          isHovered={isHovered}
          isFocused={isFocused}
          onClick={props.onSearch}
        />
      )}
      {isFocused && props.value && (
        <StyledCloseCircle onClick={() => console.log("THIS DOES NOT FIRE")} />
      )}
      {!isFocused && (
        <StyledMagnifyingGlass
          isHovered={isHovered}
          isFocused={isFocused}
          onClick={props.onSearch}
        />
      )}
    </InputContainer>
  );
});

The issue is that when the 'x' is clicked, the input looses focus, which causes the 'x' to be removed on the next render, and does not register the click event. It does, however, trigger the mousedown event.

Therefore, my two questions are:

  1. What is the order of operations when the 'x' is clicked, that leads it to registering mousedown but not click?
  2. How can I achieve the desired behavior?
Ad

Answer

You should create a separate state to control where to show/hide the Clear button. Show it onFocus even as you do now but hide it if user clicks outside of the input container or if clicks on the Clear button. You can additionally hide it onBlur but with some timeout (500-1000ms) in case if user uses keyboard instead of a mouse.

This is a CodeSnadbox example of the code below:

function App() {
  const inputContainerRef = useRef();
  const [value, setValue] = useState("");
  const [showClear, setShowClear] = useState(false);

  const onFocus = useCallback(() => {
    setShowClear(true);
  }, []);

  const onClear = useCallback(() => {
    setValue("");
    setShowClear(false);
  }, []);

  const onOutsideClick = useCallback(e => {
    // hide Clear button only if clicked outside of the container
    if (!inputContainerRef.current.contains(e.target)) {
      setShowClear(false);
    }
  }, []);

  useLayoutEffect(
    () => {
      // set the listener only if we shown the Clear button and remove the listener once we hid it
      if (showClear) {
        document.addEventListener("click", onOutsideClick);
        return () => document.removeEventListener("click", onOutsideClick);
      }
    },
    [showClear] // re-invoke if the state changes
  );

  return (
    <div className="App">
      <div className="input-container" ref={inputContainerRef}>
        <input
          value={value}
          onChange={e => {
            setValue(e.target.value);
          }}
          className="input"
          type="tetxt"
          onFocus={onFocus}
        />
        {showClear && (
          <div className="clear" onClick={onClear}>
            X
          </div>
        )}
      </div>
    </div>
  );
}
Ad
source: stackoverflow.com
Ad