joeltine
joeltine

Reputation: 1630

Set values of list of Material UI Autocompletes that fetch options dynamically

I have a Material UI Autocomplete combo-box child component class that fetches results as the user types:

...
fetchIngredients(query) { 
    this.sendAjax('/getOptions', {
      data: {
        q: query
      }
    }).then((options) => {
      this.setState({
        options: options
      });
    });
}
...
<Autocomplete
    options={this.state.options}
    value={this.state.value}
    onChange={(e, val) => {
       this.setState({value: val});
    }}
    onInputChange={(event, newInputValue) => {
      this.fetchIngredients(newInputValue);
    }}
    renderInput={(params) => {
      // Hidden input so that FormData can find the value of this input.
      return (<TextField {...params} label="Foo" required/>);
    }}
    // Required for search as you type implementations:
    // https://mui.com/components/autocomplete/#search-as-you-type
    filterOptions={(x) => x}
/>
...

This child component is actually rendered as one of many in a list by its parent. Now, say I want the parent component to be able to set the value of each autocomplete programmatically (e.g., to auto-populate a form). How would I go about this?

I understand I could lift the value state up to the parent component and pass it as a prop, but what about the this.state.options? In order to set a default value of the combo-box, I'd actually need to also pass a single set of options such that value is valid. This would mean moving the ajax stuff up to the parent component so that it can pass options as a prop. This is starting to get really messy as now the parent has to manage multiple sets of ajax state for a list of its Autocomplete children.

Any good ideas here? What am I missing? Thanks in advance.

Upvotes: 0

Views: 1750

Answers (1)

Jacob K
Jacob K

Reputation: 1183

If these are children components making up a form, then I would argue that hoisting the value state up to the parent component makes more sense, even if it does require work refactoring. This makes doing something with the filled-in values much easier and more organized.

Then in your parent component, you have something like this:

constructor(props) {
  super(props);
  this.state = {
    values: [],
    options: []
  };
}

const fetchIngredients = (query, id) => { 
  this.sendAjax('/getOptions', {
    data: {
      q: query
    }
  }).then((options) => {
    this.setState(prevState => {
      ...prevState,
      [id]: options
    });
  });
}

const setValue = (newValue, id) => {
  this.setState(prevState => {
    ...prevState,
    [id]: newValue
  };
}

render() {
  return (
    <>
      ...
      {arrOfInputLabels.map((label, id) => (
        <ChildComponent 
          id={id}
          key={id}
          value={this.state.values[id]}
          options={this.state.options[id]}
          fetchIngredients={fetchIngredients}
          labelName={label}
        />
      )}
      ...
    </>

Upvotes: 1

Related Questions