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.
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.
No one answered me, so I had to hack my own solution. The idea is
When you make a selection (onChange
), do Formik's setFieldValue('name', option[0].id)
(happy path).
To set an error condition, do Formik's setFieldValue('name', null)
. The NULL value with our NULL validation will trigger Formik's error messages automatically if you have validation defined as e.g. in Yup, myField: yup.string().required('Name is required')
.
-This error condition can be set in onBlur
or onInputChange
, but I recommend onBlur
.
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);
}
}}