Material-ui: [AutoComplete] Issue with the controlled behavior / clear

Created on 21 Dec 2015  路  15Comments  路  Source: mui-org/material-ui

I have an autocomplete field which uses

        searchText = {this.state.searchText}

like this;

          <AutoComplete
            floatingLabelText='agent input'
            ref='agentInput'
            hintText="type response"
            multiLine = {true}
            fullWidth = {true}
            searchText = {this.state.searchText}
            onNewRequest={this.sendAgentInput}
            dataSource={this.agentCommands}
          />

but when I update the

 this.setState({searchText: null })

in the onNewRequest func
it will clear the autoComplete once, but not the second time.
it seems some internal caching happens?

how to call internal methods of the MUI components?

mat-ui components more general question:
Is there a way to call the methods on that object directly like setValue?
https://github.com/callemall/material-ui/blob/master/src/auto-complete.jsx#L244-L248

I tried something like

    var field = (
        <AutoComplete 
          searchText = {this.state.searchText}
          dataSource={this.agentCommands}
      />
    );

    field.props.setValue("");

but that doesn't work. Given an instance of a component how would I access its methods?

this is a more general question, i could add it to the docs if someone could illuminate me.

bug 馃悰 Autocomplete

Most helpful comment

Can we just add a boolean prop that, when set to true, will prevent the asynchronous setState inside handleItemTouchTap?

Edit: actually, I don't think that solution would fully address the issue.

@alexprice91 explains the problem above. Even if you are controlling the AutoComplete with the searchText prop, that asynchronous setState still gets fired. In that situation you end up with searchText (the prop in a controlled use) being different than searchText (the state), which is no bueno.

I don't really like the if(this.state.searchText === currentSearchText) { solution, since it doesn't resolve the searchText state and prop incongruency. I think we should just get rid of this setState in its entirety. If the user wants to fill in the <input> whenever an option is clicked, it's trivial for them to handle it themselves using onNewRequest and the searchText prop. We shouldn't be forcing this behavior considering A) how simple it is to implement in userland and B) how difficult / janky it is to override when it's baked in.

All 15 comments

how to call internal methods of the MUI components?

This is rather react related not material-ui

you cannot call methods on elements rather on instances referenced with ref, for more information please read through this awesome blog post by the react team here.

@alitaheri how would you suggest we get / set the ref on the autocomplete input given the only API we have access to is <AutoComplete />?

@joncursi Take a look at react's docs: https://facebook.github.io/react/docs/more-about-refs.html#a-complete-example

@alitaheri I guess what I'm trying to say is that Material UI does not expose the <input> element for us to assign a ref. It's bundled underneath the <AutoComplete /> component. How do we pass the ref through AutoComplete to input?

right right. You can't do that but you can use setValue and getValue the same way see here although calling them will warn you. I would advise against it too, you should follow react's idiomatic flow of data. use searchText prop instead of calling imperative methods.

Hey, I am trying to use the props searchText, and on onNewRequest I set my searchText to (empty string) but AutoComplete still shows what the string was prior to me setting the searchText

What if we do something like this there:

    const currentSearchText = this.state.searchText;
    this.timerTouchTapCloseId = setTimeout(() => {
      if(this.state.searchText === currentSearchText) {
        this.setState({
          searchText: searchText,
        });
      }
      this.close();
      this.timerTouchTapCloseId = null;
    }, this.props.menuCloseDelay);

Can we just add a boolean prop that, when set to true, will prevent the asynchronous setState inside handleItemTouchTap?

Edit: actually, I don't think that solution would fully address the issue.

@alexprice91 explains the problem above. Even if you are controlling the AutoComplete with the searchText prop, that asynchronous setState still gets fired. In that situation you end up with searchText (the prop in a controlled use) being different than searchText (the state), which is no bueno.

I don't really like the if(this.state.searchText === currentSearchText) { solution, since it doesn't resolve the searchText state and prop incongruency. I think we should just get rid of this setState in its entirety. If the user wants to fill in the <input> whenever an option is clicked, it's trivial for them to handle it themselves using onNewRequest and the searchText prop. We shouldn't be forcing this behavior considering A) how simple it is to implement in userland and B) how difficult / janky it is to override when it's baked in.

A short-term workaround is to give your auto complete component a ref, then manually call setState to reset searchText.

this.refs.autoComplete.setState({ searchText: ''})

Thanks @greypants! Your approach worked for me.

Even if you are controlling the AutoComplete with the searchText prop, that asynchronous setState still gets fired.

@jlroettger I completely agree, that setState shouldn't be called at all when the component is controlled.
That issue has been discussed here https://github.com/callemall/material-ui/pull/5627.
However, In order not to introduce breaking change, I think that the following change would address the issue:

  handleItemTouchTap = (event, child) => {
    const dataSource = this.props.dataSource;

    const index = parseInt(child.key, 10);
    const chosenRequest = dataSource[index];
    const searchText = this.chosenRequestText(chosenRequest);
+
+    this.setState({
+      searchText: searchText,
+    });
+    this.props.onUpdateInput(searchText, this.props.dataSource, {
+      type: 'selected',
+    });

    this.timerTouchTapCloseId = setTimeout(() => {
      this.timerTouchTapCloseId = null;
-
-      this.setState({
-        searchText: searchText,
-      });
      this.close();
      this.props.onNewRequest(chosenRequest, index);
    }, this.props.menuCloseDelay);
  };

For some reason, this issue still persisted for me. There are different arguments to the third parameter depending on whether you're typing or selecting a menu item. I just honed in on that action to only set the state when it is a change event
const handleUpdateInput = (attributeName, _, { source }) => { if (source === 'change') { this.setState({ attributeName }) } }

As the components are now functional, setState is no longer working, do we have any alternative solution?

@sirajalam049 Material-UI can still be used in class components, only our demos can't. This is very handy for the legacy parts of your codebase so you don't have to migrate them to hooks (assuming you write all new features in hooks).

Was this page helpful?
0 / 5 - 0 ratings