14

I'm trying to write a component in React that will use the fetch() API to get data from a website, then use setState to set a state equal to the data, and then finally render the data. My code looks like this:

import React from 'react';

export default class Test extends React.Component {
    constructor(props){
        super(props);
        this.state = {apiInfo: 'default'};
    }

    componentDidMount(){
        fetch('https://fcctop100.herokuapp.com/api/fccusers/top/recent').then(
            function(response){
                return response.json();
            }
            ).then(function(jsonData){
                return JSON.stringify(jsonData);
            }
            ).then(function(jsonStr){
                this.setState({apiInfo: jsonStr});
                console.log(jsonStr);
            });
    }

    render(){
        return(
                <tr>
                    <td>{this.state.apiInfo}</td>
                </tr>
        );
    }
}

However, this results with an error saying I'm unable to setState of undefined. I end up rendering 'default' on my HTML. What exactly am I doing wrong here?

4
  • 3
    try to pass an arrow function to .then Commented Apr 6, 2018 at 2:20
  • Hey there! So this worked, but could you explain why the arrow function would make such a big difference? I thought the only difference for the arrow function and functions was that arrow functions weren't hoisted? Commented Apr 6, 2018 at 4:39
  • 1
    The arrow function binds this to declaration context instead of execution context. Commented Apr 6, 2018 at 5:09
  • For more explanation look at mdn Arrow Function page in "No separate this" section Commented Apr 6, 2018 at 5:24

5 Answers 5

25

Your error message is telling you exactly what the problem is:

unable to setState of undefined

So you're trying call setState as a method of an object that doesn't exist at that point. As a property of what object are you trying to call setState as a method?

this.setState({apiInfo: jsonStr});

Yes, it's your this that's the problem. At the point that you're trying to call it - i.e. inside a .then() of a fetch call - this is actually undefined. You can see this in the Chrome Devtools:

Chrome Devtools shows this=undefined I'm afraid that this is a slippery customer in JavaScript; its value can (and does) change depending upon the current context of your app.

There's several ways you can workaround this. One slightly clunky (but it works!) way is to capture your this value before you enter your .fetch() call, and assign it to another variable. You'll often see that or self variables used for this purpose, but they're just conventions. You can call the variable what you like.

Here's how I've reworked your componentDidMount() method capturing this to that, and calling that inside the .then():

componentDidMount() {
    const that = this;
    fetch("https://fcctop100.herokuapp.com/api/fccusers/top/recent")
        .then(function(response) {
            return response.json();
        })
        .then(function(jsonData) {
            return JSON.stringify(jsonData);
        })
        .then(function(jsonStr) {
            that.setState({ apiInfo: jsonStr });
            console.log(jsonStr);
        });
}

If you're comfortable using arrow functions, then another way is to replace your "normal" function call with one, like so:

.then(jsonStr => {
    this.setState({ apiInfo: jsonStr });
    console.log(jsonStr);
});

An arrow function's this is always the this that its parent defined.

Sign up to request clarification or add additional context in comments.

1 Comment

OH ok thank you for the explanation! I saw the solutions about using the arrow functions and wasn't quite sure until I saw this. Could you elaborate a little bit more on what 'this' would be referring to within the fetch() API if I didn't use the arrow function? Would it be the global object here?
3

It is saying setState is undefined because you're accessing it in the wrong context. You can either, convert the function into an arrow function or bind it to the right context. Here is an article as to When and why do we bind this to the React component method.

In your code, the change that can be made is either binding it

.then(function(jsonStr){
          this.setState({apiInfo: jsonStr});
          console.log(jsonStr);
      }.bind(this));

or using arrow function

.then((jsonStr)=>{
          this.setState({apiInfo: jsonStr});
          console.log(jsonStr);
      });

Comments

0
  • this refers to the object which calls the function.
  • every normal function has its own this however arrow functions does not (this here refers to the outside context i.e., what this is outside the function.).

So the possible solutions are

  • Use Arrow function
componentDidMount = () => {
    fetch('https://fcctop100.herokuapp.com/api/fccusers/top/recent').then(
            function(response){
                return response.json();
            }
            ).then(function(jsonData){
                return JSON.stringify(jsonData);
            }
            ).then(function(jsonStr){
                this.setState({apiInfo: jsonStr});
                console.log(jsonStr);
            });
}
  • save this in some other variable earlier and pass it as argument.
  • Bind this to the function

https://reactjs.org/docs/handling-events.html

Comments

0

Write the fetch code into separate method, then you will be able to call this.setState({}) inside the ".then(..)" statement after adding a bind to the new function inside the constructor.

Example:

constructor(props){
     super(props)
     ...
     this.myFunc = this.myFunc.bind(this)
}

myFunc = () => {
    fetch('...')
       .then(response => response.json())
       .then(data => this.setState({ apiInfo: data }));
}

componentDidMount() {
    this.myFunc()
}

Comments

-1
 componentDidMount() {
    fetch('https://fcctop100.herokuapp.com/api/fccusers/top/recent')
      .then(response => response.json())
      .then(data => this.setState({ apiInfo: data }));
  }

In your render function you cannot just do this.state.apiInfo. This data is an array of objects, each object has keys like username img and more. You can see that if you console.log(this.state.apiInfo) inside the render function and outside the return.

To access and display the each object inside of the arrays values you can map through your apiInfo array.

render() {
const { apiInfo } = this.state;
apiInfo && console.log(apiInfo.map(item => item.img));
return (
  <div>
    {apiInfo &&
      apiInfo.map(item =>  <div> {item.img} </div> )}
  </div>
);
}

The native fetch API that comes with the browser uses JavaScript promises to resolve the asynchronous response.

When the data is fetched successfully, it will be stored in the local state with React’s this.setState() method. Then the render() method will trigger again and you can display the fetched data.

Arrow functions were introduced in ES6, and an arrow function does not have its own context and will instead use the same this as the context in which it was defined. We can use this fact and pass in an arrow function to the callback.

1 Comment

it is an array of objects i console logged it

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.