BennKingy
BennKingy

Reputation: 1583

How to run JavaScript functions in order (React)

I have created a small react App that presents documents from a SharePoint site.

To make the App work correctly I am having to add setTimeouts, but I know there must be a better way using callbacks or promises or something >.<

My knowledge is lacking, so could somebody please point me in the right direction?

  // Handles what runs when the drop down is changed
  public handleDropdownChange(e) {

    // Updates dropdown select
    this.setState({ selectedOption: e.target.value, isLoading: true });

    // Checks security permissions - MAKES A GET REQUEST
    setTimeout(() => {
      this.getSecurityGroupUsers();
    }, 1000); 

   // Ghecks if user can access docs
    setTimeout(() => {
      this.checkForDocAccess();
    }, 2000); 

    // Gets documents - MAKES A GET REQUEST
    setTimeout(() => {
      this.getDocuments();
    }, 4000); 

    // Delete Mark as Reviewed property THEN displays docs
    setTimeout(() => {
      this.hideMarkAsReviewed();
    }, 8000);

  }

One of the functions:

  // Grabs the documents
  public getDocuments() {

    axios
      .get("https://bpk.sharepoint.com/_api/search/query?querytext='owstaxIdDocumentx0020Owner:" + this.state.selectedOption + "'&trimduplicates=true&rowsperpage=100&rowlimit=1000&selectproperties='LastReviewDateOWSDATE,ScheduledReviewDateOWSDATE,Path'",
        { params:{},
          headers: { 'accept': 'application/json;odata=verbose' }
        })
      .then(response =>
          response.data.d.query.PrimaryQueryResult.RelevantResults.Table.Rows.results.map(document => ({
            Type: this.checkFile(document),
            Name: document.Cells.results[6].Value.split("/").pop(),
            'Scheduled Review Date': document.Cells.results[8].Value.slice(0,-11),
            Path: document.Cells.results[6].Value.replace('https://bpkintelogydev.sharepoint.com', ''),
            Site: document.Cells.results[6].Value.split('/').slice(4).slice(0,1),
            'Last Review Date': document.Cells.results[7].Value.slice(0,-11),
            View: <a href="#" onClick={()=>window.open(document.Cells.results[6].Value + '?web=1&action=edit')}>View</a>,
            'Mark as Reviewed': <a href='#'>Mark</a>
        }))
      )
      .then(documents => {
        this.setState({documents, isLoading: true});
      })
      .catch(error => {
        //console.log(error);
      });

  }

Upvotes: 0

Views: 849

Answers (4)

T.J. Crowder
T.J. Crowder

Reputation: 1074178

Yes, callbacks or promises are what you do here. Promises would be the more modern way (not least so you can use async/await with them). setState provides a callback for when the state is set if you want that (it's an optional second argument to setState). Then your various functions (getSecurityGroupUsers, etc.) would return promises that they fulfill or reject.

In your getDocuments, for instance, you want to:

  1. return the result of calling then, and
  2. Don't have an error handler, leave that to handleDropdownChange

...and similar in the others. For any that don't already have a promise to chain on (getDocuments has the one from axios) because they use a callback-style API you can create a promise and fulfill/reject it yourself (see this question's answers for details there).

Doing that, handleDropdownChange might look something like:

// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
    // Updates dropdown select
    this.setState({ selectedOption: e.target.value, isLoading: true });

    this.getSecurityGroupUsers()
    .then(() => this.checkForDocAccess())
    .then(() => this.getDocuments())
    .then(() => this.hideMarkAsReviewed())
    .catch(error => {
        // ...handle/report error...
    });
}

or if you didn't want to start those until the state had been changed initially:

// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
    // Updates dropdown select
    this.setState(
        { selectedOption: e.target.value, isLoading: true },
        () => {
            this.getSecurityGroupUsers()
            .then(() => this.checkForDocAccess())
            .then(() => this.getDocuments())
            .then(() => this.hideMarkAsReviewed())
            .catch(error => {
                // ...handle/report error...
            });
        }
    );
}

In this particular case, async/await doesn't help much, but perhaps some. I assume handleDropdownChange shouldn't be async since it's an event handler and so nothing would handle the promise it would return, so:

// Handles what runs when the drop down is changed
public handleDropdownChange(e) {
    // Updates dropdown select
    this.setState({ selectedOption: e.target.value, isLoading: true });

    (async () => {
        try {
            await this.getSecurityGroupUsers();
            await this.checkForDocAccess();
            await this.getDocuments();
            await this.hideMarkAsReviewed();
        } catch (error) {
            // ...handle/report error...
        }
    })();
}

Notice how the entire body of the async function is in a try/catch, because nothing hooks its promise, so it's important we handle errors directly. (Ensure that "handle/report error" doesn't throw an error. :-) )

Upvotes: 2

Zohaib Ijaz
Zohaib Ijaz

Reputation: 22885

Use async await and a simple sleep function to wait between them if it is required

const sleep = time => new Promise(res => {
  setTimeout(res, time);
});

async handleDropdownChange(e) {

    // Updates dropdown select
    this.setState({ selectedOption: e.target.value, isLoading: true });

    // Checks security permissions - MAKES A GET REQUEST
    await sleep(1000);
    await this.getSecurityGroupUsers();
    
    await sleep(1000);

   // Ghecks if user can access docs
   await this.checkForDocAccess(); 

   await sleep(1000);
    // Gets documents - MAKES A GET REQUEST
    await this.getDocuments();
    
    await sleep(1000);
    // Delete Mark as Reviewed property THEN displays docs
    await this.hideMarkAsReviewed();
  }
  
  // But you methods should return a promise like this
  
  getDocuments = () => {
     return fetch('some url').then(res => res.json());
  }
  
  // Or something like
  getSecurityGroupUsers = async () => {
    const data = await axios('some url');
    return data.message;
  }

Upvotes: -1

ettakhi
ettakhi

Reputation: 187

You can use Async await for waiting promise to resolve

public handleDropdownChange = async e => {

   this.setState({ selectedOption: e.target.value, isLoading: true });

   await this.getSecurityGroupUsers();

   await this.checkForDocAccess();

   await this.getDocuments();

   await this.hideMarkAsReviewed();
}

Upvotes: 1

Max Alexander Hanna
Max Alexander Hanna

Reputation: 3853

You need a function call back!

function functionOne(x) { return x; };

function functionTwo(var1) {
    // some code
}

functionTwo(functionOne);

Upvotes: 0

Related Questions