Search code examples
javascriptreactjshtml-datalist

Clear datalist input onClick in React controlled component


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.


Solution

  • 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.