skip to Main Content

I have a class component which loads a list of applications from an external API. It then runs an additional request for each application to ping it.

It fetches the applications fine. However, it only sometimes pings the applications (calls checkPing).

Here is the component:

import axios from "axios";
import React from "react";

class Applications extends React.Component<any, any> {
    constructor() {
        super({});

        this.state = {
            applications: []
        }
    }

    async checkPing(applicationId : bigint) {
        //
    }

    async getApplications() {
        var component = this;

        await axios.get(process.env.REACT_APP_API_URL + '/applications')
            .then(async function (response) {
                await component.setState({
                    applications: response.data,
                });
            })
            .catch(function (error) {
                // handle error
                console.log(error);
            })
            .finally(function () {
                // always executed
            });

        for (let i = 0; i < component.state.applications.length; i++) {
            await component.checkPing(component.state.applications[i].id);
        }
    }

    async componentDidMount() {
        await this.getApplications();
    }

    render() {
        return (<div></div>)
    }
}

export default Applications;

2

Answers


  1. As mentioned in the comment,

    You either need to move your for loop inside then block or you can use componentDidUpdate lifecycle hook. axios call is asynchronous and your for loop is currently not waiting for it to complete.

    Here is the example of using componentDidUpdate

    import React from "react";
    import axios from "axios";
    import "./styles.css";
    
    class App extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          applications: []
        };
      }
    
      async checkPing(applicationId) {
        console.log("pinging " + applicationId);
      }
    
      async getApplications() {
        var component = this;
    
        try {
            const response = await axios.get("./data.json");
            component.setState({ applications: response.data });
        } catch (error) {
            // handle error
            console.log(error);
        }
      }
    
      componentDidMount() {
        this.getApplications();
      }
    
      componentDidUpdate() {
        for (let i = 0; i < this.state.applications.length; i++) {
          this.checkPing(this.state.applications[i].id);
        }
      }
    
      render() {
        return <div>HERE</div>;
      }
    }
    
    export default App;
    

    If you do not want to use componentDidUpdate then you can move for loop in then call like this.

    import React from "react";
    import axios from "axios";
    import "./styles.css";
    
    class App extends React.Component {
      constructor(props) {
        super(props);
    
        this.state = {
          applications: []
        };
      }
    
      async checkPing(applicationId) {
        console.log("pinging " + applicationId);
      }
    
      async getApplications() {
        var component = this;
    
        try {
            const response = await axios.get("./data.json");
            component.setState({ applications: response.data });
            for (let i = 0; i < response.data.length; i++) {
              this.checkPing(response.data[i].id);
            }
        } catch (error) {
            // handle error
            console.log(error);
        }
      }
    
      componentDidMount() {
        this.getApplications();
      }
    
      render() {
        return <div>HERE</div>;
      }
    }
    
    export default App;
    
    Login or Signup to reply.
  2. The main issue here is probably that setState() will queue the state change and not directly apply it. setState() does not return a promise. Meaning that await component.setState(...) is useless. The only way to know when React is done updating the state is to use the componentDidUpdate() hook.

    An implementation of this hook, using your example might look like this:

    import axios from "axios";
    import React from "react";
    
    class Applications extends React.Component<any, any> {
        constructor(props) {
            super(props);
    
            this.state = {
                isLoading: true,
                error: null,
                applications: [],
            }
        }
    
        async checkPing(applicationId : bigint) {
            // ...
        }
    
        async getApplications() {
            // There is no need to work with `then()`, `catch()` or `finally()`
            // when we are within async context. We can use the respective
            // keywords instead.
            try {
                const response = await axios.get(process.env.REACT_APP_API_URL + '/applications');
                this.setState({ applications: response.data });
            } catch (error) {
                // handle error
                this.setState({ error });
            } finally {
                // always executed
                this.setState({ isLoading: false });
            }
        }
    
        // Content can also be placed in `componentDidMount()` instead of
        // its own method.
        async pingApplications() {
            for (const application of this.state.applications) {
                await this.checkPing(application.id);
            }
        }
    
        componentDidMount() {
            this.getApplications();
        }
    
        componentDidUpdate(prevProps, prevState) {
            if (this.state.isLoading) return;
            if (this.state.error) return;
    
            this.pingApplications();
        }
    
        render() {
            const { isLoading, error, applications } = this.state;
    
            if (isLoading) return (
                <div className="loading">loading...</div>
            );
    
            if (error) return (
                <div className="error">{error.message}</div>
            );
    
            return (
                <div>content</div>
            );
        }
    }
    
    export default Applications;
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search