0

This is a followup question to this question

I am playing around with reactjs and here is what I am doing: Am working on retrieving data from an API and displaying the data Using axios to make a call to the API. I know that axios calls are asynchronous and promise based.

Here is the code which throws an error:

class StudentsPage extends React.Component{
   constructor(props){
     super(props);

     this.state = {
       isLoaded: false,
       studentListArray: []
     }
  }


componentDidMount(){
   console.log("<<< inside componentDidMount >>>")
    this.setState(
    { studentListArray: getListOfStudents() }
   ); 

}

render(){ 
console.log("@@@ inside render array is @@@",this.state.studentListArray)
return(
    <React.Fragment>
    <table className="table">
       <tbody>
            {this.state.studentListArray.map((student) => {
                return (<tr>
                    <td>{student.id}</td>
                    <td>{student.subject}</td>
                </tr>);

            })}
        </tbody>
    </table>
    </React.Fragment>
);
}
}

The axios API call is defined in a separate js file : studentApi.js:

import axios from "axios";
/** define the endpoint to be called */
const baseUrl = process.env.REACT_APP_API_URL + "/studentList/";

/** this function will retrieve the list of students */
export function getListOfStudents(){
   console.log("about to call api");


 /** if we use axios library then the response payload will always be inside 'data' attribute
  *  so here we use the {data} 
  */
axios.get(baseUrl).then(({data}) => {
    console.log(data);
    return data;
})
console.log("after making the call to the api")
}

So if I am trying to setState by making a call to a method that calls api using axios I get errors.

The logs show the following :

@@@ inside render array is @@@ []
<<< inside componentDidMount >>>
about to call api
after making the call to the api
@@@ inside render array is @@@ undefined
@@@ inside render array is @@@ undefined

And the exception is :

Uncaught TypeError: Cannot read property 'map' of undefined at StudentListPage.render (StudentListPage.js:75)

I am unable to understand why render is called twice after the api call ? also unable to understand why studentListArray is undefined ? Thanks

satish marathe
  • 1,073
  • 5
  • 18
  • 37
  • `getListOfStudents` function doesn't return anything, all it does is console.log - and all it CAN return is a Promise, so even if you did return something, the code you use to wouldn't work, – Jaromanda X May 26 '20 at 23:11
  • it does`return data` – satish marathe May 26 '20 at 23:13
  • 1
    no, it doesn't ... return data is inside the .then callback - returning a value from an inner function doesn't return that value from thr outer function - you need to `return axios.get.....` – Jaromanda X May 26 '20 at 23:14
  • I guess I am doing something wrong , this too does not work : `export function getListOfStudents(){ return axios.get(baseUrl).then(({data}) => { console.log(data); return data; }) }` – satish marathe May 26 '20 at 23:25
  • yes, because as I said, the `getListOfStudents` function can only return a Promise ... you'll need to learn how to use promises (I don't know enough about reactjs to tell you how to fix `this.setState( { studentListArray: getListOfStudents() } )` to use promises – Jaromanda X May 26 '20 at 23:27
  • 1
    You **also** need to change `this.setState( { studentListArray: getListOfStudents() } )` into `getListOfStudents().then(data => this.setState( { studentListArray: data } ))` – Lennholm May 27 '20 at 02:28
  • @Lennholm agreed , but I wanted to keep the API call outside of my react component code , what you have suggested definitely works though – satish marathe May 27 '20 at 08:37
  • @satishmarathe I never suggested you move the API call to your React component, all I suggested was to use the promise returned from `getListOfStudents()` properly once you had made the change mentioned in the comments above. – Lennholm May 27 '20 at 10:25
  • @Lennholm ok fair point agree. I will follow up with another question where I got this to work using callback , but I had to do some binding using this which I did not understand .... but thats a separate question . Thanks for your help – satish marathe May 27 '20 at 10:46

2 Answers2

1

First, make sure the value is returned from the Axios call:

export function getListOfStudents() {
  return axios.get(baseUrl).then(({data}) => {
    return data;
  })
}

then in the component you need to await the Promise and call setState once it resolves:

componentDidMount() {
  getListOfStudents().then(data => {
    this.setState({ studentListArray: data }); 
  });
}
Ross Allen
  • 43,772
  • 14
  • 97
  • 95
  • It seems this will work just fine. Whereas, I think a better way is to do this is to create function "getListOfStudents" as a promise function, in that use resolve(data) if the request was successful, and reject(error) in case of error. And similarly, at the time of calling function, add .catch in end to catch and throw an error if API failed. – Bhupender Keswani May 27 '20 at 03:10
  • yes the suggestion here works and I have it working in an earlier question here (https://stackoverflow.com/questions/61993663/not-sure-why-this-asynchronous-call-in-react-is-working) . I am trying to keep the api call in a separate file in this question here – satish marathe May 27 '20 at 03:27
1

I recommend you the following reading https://eloquentjavascript.net/11_async.html

As you have noticed your data is fetched and pass to the then method, but is in another context and you haven't reached in the getListOfStudents() context.

then is executed as a callback whenever the promise of axios.get is complete with a resolve result.

axios.get(baseUrl).then(({data}) => {
    console.log(data);
    return data;
})

but the call to getListOfStudents is executed asynchronously and is not going to wait for the resolve of the call of axios.get to return a value unless you return a promise in getListOfStudents and handle that promise in the call in componentDidMount, or you can make it synchronously with async/await syntax (which basically is sugar syntaxis for promises in ES7).

async function getListOfStudents(){
  const data = await axios.get(baseUrl);
  //console.log(data);
  return data;
}

class StudentsPage extends React.Component{

  /.../

  async componentDidMount(){
    const data = await getListOfStudents();
    this.setState(
    { studentListArray: data }
    ); 
  }

  /.../

}
Alejandro
  • 104
  • 4
  • Thanks @alejandro for the explanation and the link . To solve this for now I have used callback since I wanted to keep my api interaction code outside of react component in a separate file. Then from react component I made the call to the api handling function in a separate file and passed it a call back function. The promise on filfillment ( then ) I made a call to the callback method – satish marathe May 27 '20 at 08:42