2
votes

I have a form with a field that needs to show suggestions via an api call. The user should be allowed to select one of those options or not and that value that they type in gets used to submit with the form, but this field is required. I am using Formik to handle the form, Yup for form validation to check if this field is required, downshift for the autocomplete, and Material-UI for the field.

The issue comes in when a user decides not to use one of the suggested options and the onBlur triggers. The onBlur always resets the field and I believe this is Downshift causing this behavior but the solutions to this problem suggest controlling the state of Downshift and when I try that it doesn't work well with Formik and Yup and there are some issues that I can't really understand since these components control the inputValue of this field.

Heres what I have so far:

const AddBuildingForm = props => {
    const [suggestions, setSuggestions] = useState([]);
    const { values,
        errors,
        touched,
        handleChange,
        handleBlur,
        handleSubmit, 
        modalLoading,
        setFieldValue,
        setFieldTouched,
        classes } = props;

    const loadOptions = (inputValue) => {  
        if(inputValue && inputValue.length >= 3 && inputValue.trim() !== "")
        {
            console.log('send api request', inputValue)
            LocationsAPI.autoComplete(inputValue).then(response =>  {
                let options = response.data.map(erlTO => { 
                    return {
                        label: erlTO.address.normalizedAddress,
                        value: erlTO.address.normalizedAddress
                    }
                });
                setSuggestions(options);
            });
        }
        setFieldValue('address', inputValue); // update formik address value
    }

    const handleSelectChange = (selectedItem) => {
        setFieldValue('address', selectedItem.value); // update formik address value
    }

    const handleOnBlur = (e) => {
        e.preventDefault();
        setFieldValue('address', e.target.value);
        setFieldTouched('address', true, true);
    }

    const handleStateChange = changes => {
        if (changes.hasOwnProperty('selectedItem')) {
            setFieldValue('address', changes.selectedItem)
          } else if (changes.hasOwnProperty('inputValue')) {
            setFieldValue('address', changes.inputValue);
          }
    }

    return (
        <form onSubmit={handleSubmit} autoComplete="off">
            {modalLoading && <LinearProgress/>}
            <TextField
                id="name"
                label="*Name"
                margin="normal"
                name="name"
                type="name"
                onChange={handleChange}
                value={values.name}
                onBlur={handleBlur}
                disabled={modalLoading}
                fullWidth={true}
                error={touched.name && Boolean(errors.name)}
                helperText={touched.name ? errors.name : ""}/> 
            <br/>
            <Downshift  id="address-autocomplete"
                onInputValueChange={loadOptions}
                onChange={handleSelectChange}
                itemToString={item => item ? item.value : '' }
                onStateChange={handleStateChange}
               >
                {({
                        getInputProps,
                        getItemProps,
                        getMenuProps,
                        highlightedIndex,
                        inputValue,
                        isOpen,
                    }) => (
                        <div>
                            <TextField
                                id="address"
                                label="*Address"
                                name="address"
                                type="address"
                                className={classes.autoCompleteOptions}
                                {...getInputProps( {onBlur: handleOnBlur})}
                                disabled={modalLoading}
                                error={touched.address && Boolean(errors.address)}
                                helperText={touched.address ? errors.address : ""}/>
                            <div {...getMenuProps()}>
                                {isOpen ? (
                                    <Paper className={classes.paper} square>
                                        {suggestions.map((suggestion, index) =>
                                            <MenuItem {...getItemProps({item:suggestion, index, key:suggestion.label})} component="div"  >
                                                {suggestion.value}
                                            </MenuItem>
                                        )}
                                    </Paper>
                                ) : null}
                            </div>
                        </div>
                )}                
            </Downshift>

            <Grid container direction="column" justify="center" alignItems="center">
                <Button id="saveBtn"
                        type="submit" 
                        disabled={modalLoading} 
                        className = {classes.btn}
                        color="primary" 
                        variant="contained">Save</Button>
            </Grid>
        </form>
    );
}

const AddBuildingModal = props => {
    const { modalLoading, classes, autoComplete, autoCompleteOptions } = props;
    return(
        <Formik
            initialValues={{ 
                name: '', 
                address: '',
            }}
            validationSchema={validationSchema}
            onSubmit = {
                (values) => {
                    values.parentId = props.companyId;
                    props.submitAddBuildingForm(values);
                }
            }
            render={formikProps => <AddBuildingForm 
                                        autoCompleteOptions={autoCompleteOptions}
                                        autoComplete={autoComplete} 
                                        classes={classes} 
                                        modalLoading={modalLoading}
                                        {...formikProps} />}
        />
    );
}
1

1 Answers

3
votes

Got it to work. Needed to use handleOuterClick and set the Downshift state with the Formik value:

const handleOuterClick = (state) => {
    // Set downshift state to the updated formik value from handleOnBlur
    state.setState({
        inputValue: values.address
    })
}

Now the value stays in the input field whenever I click out.