Search code examples
javascriptreactjsreact-component

React doesn't update component after setState


I have a problem with updating component in React. My Autocomplete component has its defaultValue property which is linked to this.state.tags. At the time of executing render() method the this.state.tags array is not yet fetched, so it's set empty in the component. When this.state.tags array is set to it's fetched value the Autocomplete is not updated by the React.

constructor(props) {
  super(props);
  this.state = {
    tags:[],
    all_tags:[{tag: "init"}]
  };
}
componentDidMount() {

axios.post('http://localhost:1234/api/issue/getIssueById', {id: this.props.match.params.id}, { withCredentials: true })
  .then(res=>{
  var arr = [];
  res.data.tags.forEach(x=>{
      arr.push({tag: x});
  });
  this.setState((state,props)=>{return {tags: arr}});
})
.catch((e)=>{console.log(e)});
}
render() {

return (
  <Fragment>
      <Autocomplete
             multiple
             defaultValue={this.state.tags[0]}
             onChange={(event, value) => console.log(value)}
             id="tags-standard"
             options={this.state.all_tags}
             getOptionLabel={option => option.tag}
             renderInput={params => (
               <TextField
                 {...params}
                 variant="standard"
                 label="Multiple values"
                 placeholder="Favorites"
                 fullWidth
               />
             )}
           />
      </Fragment>
    );
}

Edit: If I put this inside render():

    setTimeout(()=>{
      console.log("this.state.tags: ", this.state.tags);
    }, 1000);

this.state.tags is set correctly.


Solution

  • First, you need to use this.state.tags as options in Autocomplete.

    Second your usage of setState seems problematic.

    And lastly, if you need to populate all the tags in the render, you need to use value property of the Autocomplete component.

    import React, { Fragment, Component } from "react";
    import { fetchTags } from "./fakeApi";
    import Autocomplete from "@material-ui/lab/Autocomplete";
    import TextField from "@material-ui/core/TextField";
    
    class App extends Component {
      constructor(props) {
        super(props);
        this.state = {
          tags: [],
          selectedTags: [],
          all_tags: [{ tag: "init" }]
        };
      }
    
      componentDidMount() {
        fetchTags()
          .then(res => {
           let allTags = res.data.tags.map(el => ({ tag: el }));
            this.setState({
              tags: allTags,
              selectedTags: allTags
            });
          })
          .catch(e => {
            console.log(e);
          });
      }
      onChange = value => {
          this.setState({
            selectedTags: value
          })
      }
    
      render() {
        return (
          <Fragment>
            <Autocomplete
              multiple
              value={this.state.selectedTags}
              onChange={(event, value) => this.onChange(value)}
              id="tags-standard"
              options={this.state.tags}
              getOptionLabel={option => option.tag}
              renderInput={params => (
                <TextField
                  {...params}
                  variant="standard"
                  label="Multiple values"
                  placeholder="Favorites"
                  fullWidth
                />
              )}
            />
          </Fragment>
        );
      }
    }
    
    export default App;
    

    Working Codesandbox sample with a fake api.