0

I'm new to JavaScript and React and am trying to move away from tutorials so have started making a simple app for my own learning benefit but have run into a roadblock with functions running asynchronously.

In onSearchSubmit, there is a setState which has the following in its callback:

this.findSalaryRangeMin(data.advertiser.id, data.teaser);
this.findSalaryRangeMax(data.advertiser.id, data.teaser);

How can I get these two functions above to run synchronously? findSalaryRangeMax uses this.state.salaryLower which is set in findSalaryRangeMin, but the console.log below reveals that findSalaryRangeMax is firing before findSalaryRangeMin has completed.

  findSalaryRangeMax = (advertiserId, teaser) => {
    console.log(`this.state.salaryLower: `, this.state.salaryLower);
    // ... More code
  };

I've read some resources which mention using promises, but I wasn't able to figure out how to apply it... I also am wondering whether it can be achieved with async/await.

Full(ish) Code: (I've removed some code for simplicity)

import React from "react";
import JobSearch from "../api/jobSearch"; // axios
import SearchBar from "./SearchBar";

class App extends React.Component {
  state = {
    jobTitle: "",
    advertiser: "",
    salaryLower: "",
    salaryLowerTop: "",
    salaryUpper: "",
    salaryUpperTop: ""
  };

  findSalaryRangeMin = (advertiserId, teaser) => {
    this.setState({ salaryLower: 0, salaryLowerTop: 200000 }, async () => {
      let salaryLowerPrev;
      for (var i = 0; i < 20; i++) {
        const response = await JobSearch.get(
          `http://localhost:3001/salary-range/${advertiserId}/${this.state.salaryLower}/${this.state.salaryLowerTop}/${teaser}`
        );
        console.log(response);
        if (response.data.totalCount === 1) {
          salaryLowerPrev = this.state.salaryLowerTop;
          this.setState({
            salaryLowerTop: Math.round(
              (this.state.salaryLowerTop - this.state.salaryLower) / 2 +
                this.state.salaryLower
            )
          });
        } else {
          this.setState(
            {
              salaryLowerTop: salaryLowerPrev
            },
            () => {
              this.setState({
                salaryLower: Math.round(
                  (this.state.salaryLowerTop - this.state.salaryLower) / 2 +
                    this.state.salaryLower
                )
              });
            }
          );
        }
      }
    });
  };

  findSalaryRangeMax = (advertiserId, teaser) => {
    console.log(`this.state.salaryLower: `, this.state.salaryLower);
    // ... More code
  };

  onSearchSubmit = async term => {
    const response = await JobSearch.get(
      `http://localhost:3001/job-info/${term}`
    );
    if (response.data.totalCount === 1) {
      const data = response.data.data[0];
      this.setState(
        {
          jobTitle: data.title,
          advertiser: data.advertiser.description
        },
        () => {
          this.findSalaryRangeMin(data.advertiser.id, data.teaser);
          this.findSalaryRangeMax(data.advertiser.id, data.teaser);
        }
      );
    } else {
      console.log("totalCount not equal to 1: ", response.data.totalCount);
    }
  };

  render() {
    return (
      <div>
        <SearchBar onSearchSubmit={this.onSearchSubmit} />
        <hr />
        <div>
          Job Title: {this.state.jobTitle}
          Advertiser: {this.state.advertiser}
          Salary Lower Range: {this.state.salaryLower}
          Salary Upper Range: {this.state.salaryUpper}
        </div>
      </div>
    );
  }
}

export default App;

To give some context, the app I'm trying to make, queries an API for a jobs listing site. The API response doesn't reveal a salary range for an individual job, but the salary can fairly accurately be determined by querying salary ranges.

mhd.cs
  • 711
  • 2
  • 10
  • 28
epsilon42
  • 1,863
  • 13
  • 26
  • @Dupocas Thanks for the quick reply + link. I understand it's the same concept, but I was hoping for a more JS specific context – epsilon42 Nov 11 '19 at 18:34
  • What do you mean by "js specific context" ? – Dupocas Nov 11 '19 at 18:35
  • Something in JavaScript. The accepted answer for that linked question is in a C# context. Trying to learn a new concept is hard enough, let alone having it explained in a programming language I don't know anything about. – epsilon42 Nov 11 '19 at 18:38
  • Wow. That was my bad actually. I messed up the links (sorry about that): https://stackoverflow.com/questions/44512388/understanding-async-await-on-nodejs – Dupocas Nov 11 '19 at 18:48

2 Answers2

1

You are correct in your understanding that async or promises are needed if you want the functions to run synchronously and the existing code will run to the following line findSalaryRangeMax before returning with the data needed.

async/await and promises will definitely help, but often it's worth considering a few code changes too. As an example, you could combine the two functions into a single function like

findSalaryRanges(data.advertiser.id, data.teaser)

and fetch the data, process and set state once.

some pseudo code:

findSalaryRanges = async () => {
    // get all the data needed first
    const maxSalaryData = await JobSearch.get(`http://localhost:3001/salary-range/${advertiserId}/${this.state.salaryLower}/${this.state.salaryLowerTop}/${teaser}`);
    const minSalaryData = await JobSearch.get(...);

    // process data as needed
    ...

    // set state once
    this.setState({
        salaryTop: salaryTop,
        salaryLower: salaryLower
    });
};
Samuel Goldenbaum
  • 18,391
  • 17
  • 66
  • 104
  • I ended up giving the code a complete overhaul. I couldn't do it quite the way that you proposed as my application requires to compare API results and submit another query based on result, but it pointed me in the right direction. – epsilon42 Nov 13 '19 at 02:03
  • No prob, sometimes you just need a sanity check to help you get a few ideas – Samuel Goldenbaum Nov 13 '19 at 02:06
0

setState is async, so if you are dependent on the value of the state before running the next function you could do something like:

this.setState({
  salaryLowerTop: Math.round(
    (this.state.salaryLowerTop - this.state.salaryLower) / 2 +
      this.state.salaryLower
    )
}, () => this.findSalaryRangeMax(data.advertiser.id, data.teaser))

Can I execute a function after setState is finished updating?

Jake Luby
  • 1,718
  • 5
  • 16
  • I've tried using `.then()`, but I get the following error when I use it: `Unhandled Rejection (TypeError): Cannot read property 'then' of undefined` – epsilon42 Nov 11 '19 at 18:47
  • I've edited my answer, it's been a while since I've worked w/ class components :) – Jake Luby Nov 11 '19 at 18:50