Adham El Banhawy Adham El Banhawy - 1 year ago 78
React JSX Question

How to use APIs in React with Asynchronous calls?

So I am trying to get my hands dirty with React. I am VERY determined to write the wikipedia viewer project using ReactJS. However, I hit a brick wall yesterday after fetching the data from wikipedia's API using React components. And I think it has something to do with the asynchronous call.

I defined a constant that takes in a query and injects it into the API url:

javascript
const apiUrl = query =>
`https://crossorigin.me/https://en.wikipedia.org/w/api.php?action=opensearch&format=json&search=${query}`

Then I defined a class that fetches the API asynchronously in the ComponentDidMount state:

class Wiki extends React.Component {
constructor(props){
super(props);
console.log('Inside Constructor')
}
componentWillMount(){
this.setState = {
wikiData: undefined
}
console.log('Before fetch')
fetch(apiUrl(this.props.query))
.then(console.log('fetch successful'))
.then(response => {
if(!response.ok){
throw Error("Response not ok")
}
return response
})
.then(data => data.json())
.then(data => {
this.setState ={
wikiData: data
}
console.log(data)
})

}
render(){
if(!this.state.wikiData == null) return <p>No answer</p>
else{
return (
<div>
<h1>Something happened</h1>
<h2>{this.state.wikiData[0]}</h2>
</div>
)
}
}


}

When I call this in the app component, I get a TypeError sying "Cannot read property 'wikiData' of null" after which the data returns from the call and is not null anymore, but that causes the rendering to break.
Console messaqges
I did try using componentDidMount instead but that doesn't work either.
I don't know what to do and I want to get an understanding of this process.
Here's my codepen :
https://codepen.io/Banhawy/pen/eEmQBW?editors=1010

I want to know how to render the component only after the call to the API has returned with data so I can access and process it before rendering.

Answer Source

I've amended your code a bit, and made some comments that will hopefully help you out.

class Wiki extends React.Component {

  constructor(props) {
    super(props);

    // this.state, not this.setState.
    // Also, it helps if you set your initial value to the value
    // to what you expect from your fetch (ie an array, albeit empty)
    this.state = { wikiData: [] }
  }

  // componentDidMount rather than componentWillMount
  // https://daveceddia.com/where-fetch-data-componentwillmount-vs-componentdidmount/
  componentDidMount() {
    fetch(apiUrl(this.props.query))
      .then(response => {
      .then(data => data.json())
      .then(data => this.setState = { wikiData: data }
      });
  }

  render() {

    // Check to see if your initial state is empty or not
    if (!this.state.wikiData.length) return <p>No answer</p>

    // No need for your else here. If your state is not empty, the first
    // return doesn't run, but this one does as a default.
    return (
      <div>
        <h1>Something happened </h1>
        <h2>{this.state.wikiData[0]}</h2>
      </div>
    )
  }

}
Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download