Need Help with Using APIs in React (Wiki Project)

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:

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.

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 :

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.

  this.setState = {
    wikiData: undefined
  }

Should be this.state instead of this.setState. Also it better be inside constructor.

if(!this.state.wikiData == null) return <p>No answer</p>

Why are you comparing to null? wikiData clearly will be undefined before response fulfills

2 Likes

wikiData is a property of this.state. Since this.state is null trying to access a property of null

if(!this.state.wikiData == null) return <p>No answer</p>

will throw a TypeError.

The more subtle problem is that you are calling setState and fetch in componentWillMount. As per the React doc:

componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state synchronously in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method.

_This is the only lifecycle hook called on server rendering. Generally, we recommend using the constructor() instead.

In a nutshell when you call setState in componentWillMount it doesn’t really set the state. So when it reaches render this.state is null. If you want to initialize state do it in the constructor.

this.state = {
   wikiData: null
}

and then you can do

if(this.state.wikiData == null) return <p>No answer</p>

in the render. Also move the fetch into componentDidMount.

componentDidMount() is invoked immediately after a component is mounted. Initialization that requires DOM nodes should go here. If you need to load data from a remote endpoint, this is a good place to instantiate the network request. Setting state in this method will trigger a re-rendering.

The Component Lifecycle

1 Like

Thank you both for your explanations! I moved the set state to the constructor and now I understand that it’s the state of the component class not the individual functions.

I was trying to use

if(this.state.wikiData == null) return <p>No answer</p>

to have “No Answer” or “Loading” text display until the fetch is completed and wikiData is not null which I thought would trigger the else statement. However it does not.

I only started using react a week ago and I am trying to wrap my head around the react architecture.

1 Like

Do read the component lifecycle article. It is one of the more important aspects about react.

1 Like

this.state.wikiData is never going to be null in your code. It’s going to be undefined. These are different data types

1 Like

Thanks guys I now officially understand how to fetch API data in react and I’m gonna read more about component states. Now all I have to do is extract and display the data.
Cheers :beer:

Hey keep us updated. Im also trying to start off the gate with React. I did the random quote one in react. Going to start the wiki next!

One thing that helped me make sense of things was splitting up my api call, and the setting of my state. So that an auxiliary method made the call, returned an object, then the component did mount lifecycle method used this object to set the state.

componentDidMount() {
        getQuote(res => {
            this.setState({quote: res.quote, author: res.author})

        })
}

var getQuote = function(handleResponse) {
    $.ajax({
        cache: false,
        url: 'https://api.forismatic.com/api/1.0/',
        jsonp: 'jsonp',
        dataType: 'jsonp',
        cache: 'false',
        data: {
            method: 'getQuote',
            lang: 'en',
            format: 'jsonp'
        },
        success: (res) => {
            const author = (res.quoteAuthor != '')
                ? res.quoteAuthor
                : 'anon'
            handleResponse({quote: res.quoteText, author: author})
        }
    }).catch((error) => {
        console.log(error)
        return {quote: 'no quote loaded', author: 'try again'}
    })
}

Of course yours is a more complicated use case but I think it’d still help declutter your lifecycle method.

1 Like

I think that is a great idea separating the API call and the componenetDidMount state. It’s also nice seeing how the .ajax method can be used inside a component.
I finally figured out how to implement a loading screen and a failed screen this morning

class Wiki extends React.Component {
    constructor(props){
        super(props);
      this.state = {
        wikiData: undefined,
        requestFailed: false
      }
    }
    componentDidMount(){
      console.log('Before fetch')
        fetch(apiUrl(this.props.query))
        .then(response => {
            if(!response.ok){
                throw Error("Response not ok")
            }
            return response
        })
        .then(data => data.json())
        .then(data => {
            this.setState({
                wikiData: data
            })
        }, ()=>{
          this.setState({
            requestFailed: true
          })
        })
    }
    render(){
        if(this.state.requestFailed) return <h1>Request Failed</h1>
        if(!this.state.wikiData) return <h2>Loading</h2>
        return (
            <div>
                <h1>Something happened</h1>
                <h2>It's {this.state.wikiData[0]}</h2>
            </div>
        )
    }
}

I probably will do another API project soon after this one and will try out the ajax method and compare.
Thanks for your feedback and keep it up!

1 Like

Thanks :slight_smile:

But also the ajax call isn’t in the component, that throws an error for me, I just copied pasted them together so you can see what the getQoute() method is doing for context… I imported it from another file.

heres the actual code githubRepo

and site

Looks neat. Yeah I suspected that you did that, and now I’m curious why it didn’t work inside the component. I just created a separate method inside mine to call the API and called it inside componentDidMount and it worked with fetch().

Also heads-up for when you do the wiki project and you develop locally you might run into an issue connecting to the wiki API from localhost like I did. I found a workaround so let me know if you run into that problem.

Idk what is was that I had did wrong, lol.

Luckily didn’t run into that problem, and I just finished it a lil while ago.

check it out! wiki

1 Like

This looks great! And your code is clean!
I finished mine a few days ago and published it here

I’m on the twitch API project and I ran into the same problem again with CORS and fetching data from developing environment with .fetch(), but I tried out your jquery .ajax and it works without issues. So thank you for sharing it really helped me out!

I think the reason why fetch might be giving you problems is because vanilla fetch does not support jsonp formats. Which is the return from api calls to another url.

AJAX does support that format.

To use api calls to other URLs with fetch you have to use one that allows for jsonp.
Here’s an example of a fetch that allows for jsonp that you can install. I personally haven’t used it tho so can’t give a review of the package.

I thought that might be a possibility, and when I tried adding jsonp to the headers in the fetch request it didn’t work. But I’ll try that package anyway and see if it turns out any different. :+1:t4: