2

I have a html5 input with an associated datalist inside a React controlled component. I want to clear the text when the input field is clicked or receives focus so all options are displayed for selection. I've followed Alfred's excellent answer in this question but am unable to achieve quite the same result in a React controlled component. Unfortunately, calling blur inside the onClick handler prevents my users from typing more than a single character because focus is (of course) lost.

How can I maintain the ability for users to type but clear the text and show the full set of options whenever the text box is clicked?

import React, { useState } from "react";

const MyForm = () => {
  const [options, setOptions] = useState(["Apples", "Oranges", "Bananas", "Grapes"]);

  const handleChange = (event) => {
    event.target.blur();
  };

  const clear = (event) => {
    event.target.value = "";
  };

  return (
    <>
      <input
        type="input"
        list="optionsList"
        onChange={handleChange}
        onFocus={clear}
        placeholder="Select an option"
      />
      <datalist id="optionsList">
        {options.map((o) => (
          <option key={o}>{o}</option>
        ))}
      </datalist>
    </>
  );
};

export default MyForm;

Note that I've also tried a version of this that calls clear onClick rather than onFocus. That keeps me from needing to call blur() in handleChanges so the problem typing is solved. But, this requires that I click twice to see the full set of options because the list of options seems to be presented before the box is cleared.

Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
Matt Frei
  • 379
  • 1
  • 4
  • 13
  • It's not a controlled component. Are you sure what you asking? If you trying to make it a controlled component you need to control it via the state. Or are you just want to make it work like in the answer you share? Which is an uncontrolled component - then its straight forward using `ref`/event object like in vanilla JS. – Dennis Vash Jan 05 '21 at 07:36
  • The list of options is controlled via state and I'll be using state to store the selected items as well. Help along either route that leads where I want to go is welcome. – Matt Frei Jan 05 '21 at 07:47

2 Answers2

2

Saw your comment on one of my question, so I figured I'd post it here as an answer instead.

Based on your use case, here is what I think you will need

import React, { useState } from "react";

const MyForm = () => {
  const [options, setOptions] = useState(["Apples", "Oranges", "Bananas", "Grapes"]);

  const handleChange = (event) => {
    if (!event.nativeEvent.inputType) {
      event.target.blur();
    }
  };

  const clear = (event) => {
    event.target.value = "";
  };

  return (
    <>
      <input
        type="input"
        list="optionsList"
        onChange={handleChange}
        onClick={clear}
        onFocus={clear}
        placeholder="Select an option"
      />
      <datalist id="optionsList">
        {options.map((o) => (
          <option key={o}>{o}</option>
        ))}
      </datalist>
    </>
  );
};

export default MyForm;

In order to prevent handleChange from blocking text input normally, you will have to check for event.nativeEvent.inputType, as onChange triggered by clicking on datalist will not have an inputType value. So in this case we will only perform the input blur when it is populated by datalist and keep the focus for any other events.

I have also added an additional onClick handler to clear the input regardless whether the input is already in focus or not.

Alfred
  • 644
  • 4
  • 12
0

I guess you actually want to have input value as a state, and not the options.

Therefore possible controlled component implementation should be:

const options = ["Apples", "Oranges", "Bananas", "Grapes"];

const EMPTY_INPUT = "";

const MyForm = () => {
  const [value, setValue] = useState(EMPTY_INPUT);

  const onFocusClear = () => {
    setValue(EMPTY_INPUT);
  };

  const onChange = ({ target: { value } }) => {
    setValue(value);
  };

  return (
    <>
      <input
        value={value}
        type="input"
        list="optionsList"
        onChange={onChange}
        onFocus={onFocusClear}
        placeholder="Select an option"
      />
      <datalist id="optionsList">
        {options.map((o) => (
          <option key={o}>{o}</option>
        ))}
      </datalist>
      Value: {value}
    </>
  );
};

Edit datalist example


And making it an uncontrolled component is pretty simple by removing the onChange. Now you have the input value in ref.current.value (Not so useful use case, just an example).

const MyForm = () => {
  const inputRef = useRef();

  const onFocusClear = () => {
    inputRef.current.value = ''
  };

  return (
    <>
      <input
        type="input"
        list="optionsList"
        onFocus={onFocusClear}
        placeholder="Select an option"
      />
      <datalist id="optionsList">
        {options.map((o) => (
          <option key={o}>{o}</option>
        ))}
      </datalist>
    </>
  );
};
Dennis Vash
  • 50,196
  • 9
  • 100
  • 118
  • Thanks for the reply. I likely will use state for the input as well. I hadn't quite made it that far yet. I want to use state for options as well as they are dynamically changed by other components in the app (not shown in my simplified example). Unfortunately, this still doesn't solve my issue of clearing the value while still allowing typing to be interrupted when focus is lost. – Matt Frei Jan 05 '21 at 20:00
  • What does it mean "clearing the value while still allowing typing to be interrupted when focus is lost"? The answer behaves exactly like in the question you share. – Dennis Vash Jan 05 '21 at 20:03
  • It's not quite the same for me. You've completed the controlled component but that's not the problem I have. By removing the blur() function in the original, your solution only clears the text when the input newly receives focus. If already focused, clicking doesn't do anything. If I restore the blur function, I can't continue to type beyond the first character because focus is lost. I've tried changing onFocus to onClick. It's a bit better but it takes 2 clicks to achieve the desired result. The first one clears the value and the second one produces the full list of options. – Matt Frei Jan 05 '21 at 23:15
  • Here is my desired behavior: A single click on the input always clears the text to show the full set of options regardless of whether the input is already in focus. The user can also type naturally in the box and results are filtered. Also, my plan is to only update the value state onBlur but I think that's beside the point of my question. – Matt Frei Jan 05 '21 at 23:20