Reputation: 77
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.
Upvotes: 0
Views: 7970
Reputation: 63589
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>
)
}
}
Upvotes: 1
Reputation: 1171
There is a few errors in your code, let me extend.
First you should have your component default state on your class constructor, so move your first three lines on the method componentWillMount.
Second, your default state is wrong:
this.setState = {
wikiData: undefined
}
this.setState
is a function, to set a default state you should use this.state
.
Upvotes: 0
Reputation: 3551
You're right when saying that the render method is called before the service answered any response.
So you have to condition the rendering by displaying for example a loading spinner while the wikiData field is undefined and when the service returns data, you will render wikiData.
Upvotes: 1
Reputation: 5323
You made a typo in componentWillMount
, you wrote this.setState
instead of this.state
. I also advise you to move this declaration to the constructor :
class Wiki extends React.Component {
constructor(props) {
super(props);
this.state = {
wikiData: undefined
}
}
...
}
Upvotes: 0