Reactjs Select Form is updated with one-click delay

Viewed 1097

I am trying to show the results from an API query using axios. The query changes after a value is selected in a dropdown select form. The issue i have is that it seems that the "current_newspaper" value is not being updated in a timely manner, so what happens is the page refreshes after a second click, but not just after i select the right option.

For example, i click on option "newstest2", then the query "getNews" is doing a query and refreshing the info with PREVIOUS value ("newstest"). But then, from this situation, when i click to option "newstest3", then the query is done with the value from "newstest2". So it seems that it has some kind of delay. I think the issue is related with the states, it could be because this.state is not being updated in a timely manner or because i am doing the query before that data is refreshed.

Do you know what could be the issue? I am very stuck here.

Thank you!

class App extends Component {

  constructor(props) {
    super(props);
    this.state = {
        news: [],
        isLoading: true,
        errors: null,
        current_newspaper: "newstest"
      };
    this.handleChange = this.handleChange.bind(this);
  }

  getNews() {
  // We're using axios instead of Fetch
  axios
    // The API we're requesting data from
    .post("http://127.0.0.1:5000/api/v1/"+this.state.current_newspaper)
    // Once we get a response, we'll map the API endpoints to our props
    .then(response =>
      response.data.map(newses => ({
        title: `${newses.title}`,
        description: `${newses.description}`,
        url: `${newses.url}`,
        lastdate: `${newses.scrape_lastdate}`
      }))
    )
    // Let's make sure to change the loading state to display the data
    .then(news => {
      this.setState({
        news,
        isLoading: false
      });
    })
    // We can still use the `.catch()` method since axios is promise-based
    .catch(error => this.setState({ error, isLoading: false }));
  }

  componentDidMount() {
    this.getNews();
  }

  handleChange(event) {
    this.setState({current_newspaper: event.target.value})
    this.getNews();
  }

  handleSubmit(event){
    event.preventDefault();
  }

  render(){
    const { isLoading, news } = this.state;
  return (

    <div className="App">
      <Navbar bg="dark" variant="dark">
        <Navbar.Brand href="#home">
          <img
            alt=""
            src="/logo.svg"
            width="30"
            height="30"
            className="d-inline-block align-top"
            />
          {' '}
          testpage
        </Navbar.Brand>
      </Navbar>
      <Container>
        <Row>
          <Col>
              <Form onSubmit={this.handleSubmit}>
              <Form.Group controlId="exampleForm.SelectCustom" >
                <Form.Control as="select" onChange={this.handleChange} value={this.state.current_newspaper} custom>
                  <option value="newstest">newstest</option>
                  <option value="newstest2">newstest2</option>
                  <option value="newstest3">newstest3</option>
                </Form.Control>
              </Form.Group>
            </Form>
          </Col>
        </Row>
      </Container>
  <Container>
    <Row>
      {!isLoading ? (
        news.map(newses => {
          const { title, description, url, lastdate } = newses;
          return (
            <Card key = {title} style={{ width: '18rem' }}>
            <Card.Body>
              <Card.Link href={url}>
                {title}
              </Card.Link>
              <Card.Text>
                {description}
              </Card.Text>
              <Card.Text>
                {lastdate}
              </Card.Text>
            </Card.Body>
          </Card>
      );
    })
  ) : (
    <p>Loading...</p>
  )}
    </Row>
  </Container>
1 Answers

setState does not immediately update the state - instead, the it's a request for a change of state. This means in handleChange, when getNews is called immediately after setState, the state is still the old state.

Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.

You'll want to do this.getNews() in componentDidUpdate or in a callback:

handleChange(event) {
  this.setState({ current_newspaper: event.target.value }, () => {
    this.getNews();
  })
}

or

componentDidUpdate(prevProps) {
  if (this.props.current_newspaper !== prevProps.current_newspaper) {
    this.getNews();
  }
}
Related