react-bootstrap-typeahead

React-Bootstrap-Typeahead: Handle case when user types and makes selection invalid


I need a way to identify in react-bootstrap-typeahead whether after a selection someone has started typing, which makes the selected value invalid. The only valid value is after a click-selection from the prompts. Otherwise for any keyboard modifications, I need to flag this and pass an error condition to my outer form (Formik's values), to immediately show an error.

enter image description here

I wanted to make onInputChange set a blank value because any post-selection typing is always invalid. But that completely blocked all options for me as I start typing. I need to keep the option-matching, but just set the value to NULL so that Formik/React-Bootstrap can flag it is invalid and show an error immediately. (Also, before the first click-selection ("first typing"), it's OK not to flag it.)

<AsyncTypeahead
    isLoading={isLoading} 
    onSearch={(query) => {
        fetchChangeUserSearchResults(query)                         
    }}
    options={searchResults}
    labelKey={option => `${option.full_name}`}

    /* onChange & onInputChange. Wire value into Formik form, using Formik's setFieldValue */
    onChange={option => {
         if (option.length) {
             setFieldValue(props.name, option[0].full_name);
         }
    }}

    onInputChange = {(text, e) => {
         // I'm guessing I need to do something here: this catches keyboard events
         // Need to catch the "mid-flight" case where current string does not match any 
         // of the available options. 
         setFieldValue(props.name, ''); // This flags the Formik error immediately, 
                                        // but the options no longer show on typing
    }}
/>

The Formik error auto-fires when the value of a given control is blank, which is what I want. I need to simulate that, but keep the options displayed on typing on any input change from the click selection.


Solution

  • No one answered me, so I had to hack my own solution. The idea is

    To check the currently displayed value, we can access the underlying input element (we have a special way of checking it: document.querySelector('input[id="savedId"]'); where the ID is saved using inputProps={{'id':'savedId'}}. We need this input-field value to be compared against the prior (correctly set) value for any discrepancy in onBlur -- the prior set value can be saved as a state variable, in my example selection, for this comparison.

    const [selection, setSelection] = useState(null);
    
    const formikHook = useFormikContext(); // If outside Formik, this is how we get the context
                                           // otherwise directly call setFieldValue(..) below
    
    <AsyncTypeahead
        ...
      inputProps={{'id': 'inputElemOfTypehead'}} /* By giving an ID to the underlying Input 
                                                   we can check it easily */
    
      // onChange is on valid option selection (a menu click)
      onChange={option => {
        // Set the value in Formik's value map (this is a valid selection)
        formikHook.setFieldValue('myfield', option[0].id);
        // In addition, save this selection in a state var. for later comparisons
        setSelection(option[0].yourLabel); // use whatever is used for label string    
      }}
      // onInputChange is on pressing keystrokes. Can be used for error checks
      onInputChange={(text, e) => { 
        // We can check for some conditions onInputChange or onBlur
        // e.g. in this example, check for empty string
        if (!text.trim().length) {
             formikHook.setFieldValue('myfield', null); 
        }
      }}
      // onBlur is on leaving the control. Can be used for error checks
      onBlur={(e) => {
        // We can check for some conditions onInputChange or onBlur
        // e.g. in this example, check for string in the *underlying input element* 
        // not matching the saved "selection" var
        // Note that we gave the underlying element an ID, so we can find it easily
        var inputElement = document.querySelector('input[id=inputElemOfTypehead]');
        if (selection && inputElement.value != selection) {
             formikHook.setFieldValue('myfield', null); 
        }
      }}