15
votes

Currently trying to use Material UI's Autocomplete component with Formik. So far things like text fields and traditional selects from Material-UI play very nice with Formik. Implementing Autocomplete is not the case. Formik's onChange handler doesn't seem to update the value for my city_id. I know Autocomplete is still not apart of Material-UI's core library but was still seeing if something like this was a possibility at the moment.

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from 'formik';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import Button from '@material-ui/core/Button';

import { cities } from '../data/cities';

import "./styles.css";

const initialValues = {
  city_id: '',
};

const submit = params => {
  alert(`Value for city_id is: ${params.city_id}`);
};

function App() {
  return (
     <Formik
      initialValues={ initialValues }
      onSubmit={ submit }
    >
      {({
        handleChange,
        values,
      }) => (
        <Form>
          <Autocomplete
            id="city_id"
            name="city_id"
            options={ cities }
            groupBy={ option => option.state }
            getOptionLabel={ option => option.name }
            style={{ width: 300 }}
            renderInput={params => (
              <TextField
                { ...params }
                onChange={ handleChange }
                margin="normal"
                label="Cities"
                fullWidth
                value={ values.city_id }
              />
            )}
          />

          <Button
            variant="contained"
            color="primary"
            type="submit"
          >
            Submit
          </Button>
        </Form>
      )}
    </Formik>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Edit angry-goldstine-8offj

3

3 Answers

25
votes

Your problem is that handleChange won't work the way you are doing.

If you take a look at the handleChange docs:

General input change event handler. This will update the values[key] where key is the event-emitting input's name attribute. If the name attribute is not present, handleChange will look for an input's id attribute. Note: "input" here means all HTML inputs.

Which should work fine, but the problem is that the TextField inside Autocomplete will only trigger handleChange when you type something on it, and the value will be the text, not the id or other property you want, so you need to move handleChange to the Autocomplete.

And there is another problem, you can't use handleChange in the Autocomplete because it doesn't references the input you want and it also have different parameters from the normal onChange of the input, as you can see in the docs.

onChange
func
Callback fired when the value changes.
Signature:
function(event: object, value: any) => void
event: The event source of the callback
value: null

So what you need to do is use setFieldValue and pass it to Autocomplete like

onChange={(e, value) => setFieldValue("city_id", value)}

You need to pass the name of your field and what value you want to get.

Here is a working example

5
votes

@vencovsky has provided the correct answer that is still working for me with Material UI 14.10.1.

I'm adding a bit more to it as I have my field set to required in using Yup validation.

To get this to work correctly I have the following: Yup config:

validationSchema = {
    Yup.object().shape({
        contact: Yup.string().max(255).required('Contact is required'),
    })
}

react:

<Autocomplete
    id="contact-autocomplete"
    options={contacts}
    getOptionLabel={(contact) => `${contact?.firstName} ${contact?.lastName}`}
    onChange={(e, value) => setFieldValue("contact", value?.id || "")}
    onOpen={handleBlur}
    includeInputInList
    renderInput={(params) => (
        <TextField
            {...params}
            error={Boolean(touched.contact && errors.contact)}
            fullWidth
            helperText={touched.contact && errors.contact}
            label="Contact Person"
            name="contact"
            variant="outlined"
        />
    )}
/>

When the user click on the Autocomplete element, it fires the onOpen which runs the Formik onBlur and marks the field as touched. If an item is then not picked, Formik flags the field and displays the Contact is required validation message.

1
votes

You have to add onChange = {(event, value) => handleChange(value)} in Autocomplete tag as

import React from "react";
import ReactDOM from "react-dom";
import { Formik, Form } from 'formik';
import TextField from '@material-ui/core/TextField';
import Autocomplete from '@material-ui/lab/Autocomplete';
import Button from '@material-ui/core/Button';

import { cities } from '../data/cities';

import "./styles.css";

const [cityId,setCityId]=React.useState({city_id:''});

const handleChange=(value)=>{
  // Here is the value is a selected option label or the new typed value
  setCityId({city_id:value});
}


function App() {
  return (
     <Formik
      initialValues={ cityId }
      onSubmit={() => {
        alert(`Value for city_id is: ${cityId.city_id}`);
      }}
    >
      {({
        handleChange,
        values,
      }) => (
        <Form>
          <Autocomplete
            id="city_id"
            name="city_id"
            options={ cities }
            groupBy={ option => option.state }
            getOptionLabel={ option => option.name }
            style={{ width: 300 }}
            onChange = {(event, value) => handleChange(value)}
            renderInput={params => (
              <TextField
                { ...params }
                onChange={ handleChange }
                margin="normal"
                label="Cities"
                fullWidth
                value={ values.city_id }
              />
            )}
          />

          <Button
            variant="contained"
            color="primary"
            type="submit"
          >
            Submit
          </Button>
        </Form>
      )}
    </Formik>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

If onChange don't work you can use onInputChange as well.