356

I'm new in React and I'm trying to write an app working with an API. I keep getting this error:

TypeError: this.setState is not a function

when I try to handle the API response. I suspect it's something wrong with this binding but I can't figure out how to fix it. Here's the code of my component:

var AppMain = React.createClass({
    getInitialState: function() {
        return{
            FirstName: " "
        };
    },
    componentDidMount:function(){
        VK.init(function(){
            console.info("API initialisation successful");
            VK.api('users.get',{fields: 'photo_50'},function(data){
                if(data.response){
                    this.setState({ //the error happens here
                        FirstName: data.response[0].first_name
                    });
                    console.info(this.state.FirstName);
                }

            });
        }, function(){
        console.info("API initialisation failed");

        }, '5.34');
    },
    render:function(){
        return (
            <div className="appMain">
            <Header  />
            </div>
        );
    }
});
Let Me Tink About It
  • 13,762
  • 16
  • 86
  • 184
Ivan
  • 15,869
  • 6
  • 22
  • 32

15 Answers15

405

The callback is made in a different context. You need to bind to this in order to have access inside the callback:

VK.api('users.get',{fields: 'photo_50'},function(data){
    if(data.response){
        this.setState({ //the error happens here
            FirstName: data.response[0].first_name
        });
        console.info(this.state.FirstName);
    }

}.bind(this));

EDIT: Looks like you have to bind both the init and api calls:

VK.init(function(){
        console.info("API initialisation successful");
        VK.api('users.get',{fields: 'photo_50'},function(data){
            if(data.response){
                this.setState({ //the error happens here
                    FirstName: data.response[0].first_name
                });
                console.info(this.state.FirstName);
            }

        }.bind(this));
    }.bind(this), function(){
    console.info("API initialisation failed");

    }, '5.34');
Davin Tryon
  • 64,963
  • 15
  • 144
  • 131
153

You can avoid the need for .bind(this) with an ES6 arrow function.

VK.api('users.get',{fields: 'photo_50'},(data) => {
        if(data.response){
            this.setState({ //the error happens here
                FirstName: data.response[0].first_name
            });
            console.info(this.state.FirstName);
        }

    });
goodhyun
  • 4,568
  • 3
  • 32
  • 25
  • 1
    This works well. In fact, the keyword of function should not be shown up in a file of es6. – JChen___ Jul 12 '16 at 14:32
  • 7
    Your answer helped me:-) Using an ES6 class and RN 0.34, I found two ways to bind "this" to a callback function. 1) `onChange={(checked) => this.toggleCheckbox()}`, 2) `onChange={this.toggleCheckbox.bind(this)}`. – devdanke Oct 16 '16 at 00:12
  • This is good as long as you don't need to support old browsers. – User Mar 30 '17 at 20:15
  • Perfect solution – Hitesh Sahu Jul 26 '17 at 09:11
  • 2
    GMsoF, those two solutions work because a) when you do `.bind(this)`, it sets the value of `this` to the parent context where `this.toggleCheckbox()` is called from, otherwise `this` would refer to where it was actually executed. b) The fat arrow solution works because it retains the value of `this`, so it is helping you by not violently changing the value of `this`. In JavaScript, `this` simply refers to the current scope, so if you write a function, `this` is that function. If you put a function inside it, `this` is inside that child function. Fat arrows keep the context of where called from – agm1984 Nov 03 '17 at 19:11
  • It allows you to nest function calls exactly like this and not have to worry about always remembering to attach `.bind(this)` on it. Look at devdanke's comment again and look how the only difference is `this.foo.bind(this)` vs. `() => this.foo()` – agm1984 Nov 03 '17 at 19:14
  • In 2019, this is the best answer now. – Harry12345 Jul 04 '19 at 11:01
53

React recommends bind this in all methods that needs to use this of class instead this of self function.

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

 onClick () {
     this.setState({...})
 }

Or you may to use arrow function instead.

uruapanmexicansong
  • 2,520
  • 1
  • 15
  • 20
40

you could also save a reference to this before you invoke the api method:

componentDidMount:function(){

    var that = this;

    VK.init(function(){
        console.info("API initialisation successful");
        VK.api('users.get',{fields: 'photo_50'},function(data){
            if(data.response){
                that.setState({ //the error happens here
                    FirstName: data.response[0].first_name
                });
                console.info(that.state.FirstName);
            }
        });
    }, function(){
        console.info("API initialisation failed");

    }, '5.34');
},
user2954463
  • 2,256
  • 1
  • 21
  • 35
16

You just need to bind your event

for ex-

// place this code to your constructor

this._handleDelete = this._handleDelete.bind(this);

// and your setState function will work perfectly

_handleDelete(id){

    this.state.list.splice(id, 1);

    this.setState({ list: this.state.list });

    // this.setState({list: list});

}
g8bhawani
  • 664
  • 4
  • 8
14

use arrow functions, as arrow functions point to parent scope and this will be available. (substitute of bind technique)

Pranav Kapoor
  • 161
  • 1
  • 3
10

Now ES6 have arrow function it really helpful if you really confuse with bind(this) expression you can try arrow function

This is how I do.

componentWillMount() {
        ListApi.getList()
            .then(JsonList => this.setState({ List: JsonList }));
    }

 //Above method equalent to this...
     componentWillMount() {
         ListApi.getList()
             .then(function (JsonList) {
                 this.setState({ List: JsonList });
             }.bind(this));
 }
Sameel
  • 113
  • 1
  • 5
9

You no need to assign this to a local variable if you use arrow function. Arrow functions takes binding automatically and you can stay away with scope related issues.

Below code explains how to use arrow function in different scenarios

componentDidMount = () => {

    VK.init(() => {
        console.info("API initialisation successful");
        VK.api('users.get',{fields: 'photo_50'},(data) => {
            if(data.response){
                that.setState({ //this available here and you can do setState
                    FirstName: data.response[0].first_name
                });
                console.info(that.state.FirstName);
            }
        });
    }, () => {
        console.info("API initialisation failed");

    }, '5.34');
 },
Hemadri Dasari
  • 29,321
  • 31
  • 106
  • 146
7

Now in react with es6/7 you can bind function to current context with arrow function like this, make request and resolve promises like this :

listMovies = async () => {
 const request = await VK.api('users.get',{fields: 'photo_50'});
 const data = await request.json()
 if (data) {
  this.setState({movies: data})
 }
}

With this method you can easily call this function in the componentDidMount and wait the data before render your html in your render function.

I don't know the size of your project but I personally advise against using the current state of the component to manipulate datas. You should use external state like Redux or Flux or something else for that.

Quentin Malguy
  • 249
  • 5
  • 6
6

Here THIS context is getting changed. Use arrow function to keep context of React class.

        VK.init(() => {
            console.info("API initialisation successful");
            VK.api('users.get',{fields: 'photo_50'},(data) => {
                if(data.response){
                    this.setState({ //the error happens here
                        FirstName: data.response[0].first_name
                    });
                    console.info(this.state.FirstName);
                }

            });
        }, function(){
        console.info("API initialisation failed");

        }, '5.34');
Shubham Gupta
  • 2,476
  • 1
  • 7
  • 18
3

I have the same error of

TypeError: setState is not a function

but the cause is silly. Posting it as a response here to hopefully save people who might be making the same mistake.

Instead of

  const { state, setState } = React.useState(false);

Use

  const [ state, setState ] = React.useState(false);

Square brackets and not curly brackets!

Yong
  • 103
  • 1
  • 5
2

If you're doing this and still having an issue, my problem is I was calling two variables the same name.

I had companies as an object brought in from Firebase, and then was trying to call this.setState({companies: companies}) - it wasn't working for obvious reasons.

LukeVenter
  • 369
  • 3
  • 16
1

Use from arrow function to handle action.

tohidmahmoudvand
  • 235
  • 2
  • 12
1

This is mainly incompatibility problem between react, react-dom, and enzyme.

Try install the following as I did to solve the problem:

[...]
    "react": "^18.0.0-beta-f320ef88f-20211116",
    "react-dom": "16.14.0",
    "enzyme": "^3.11.0",
    "enzyme-adapter-react-16": "^1.15.6"
[...]
Elikill58
  • 3,190
  • 22
  • 17
  • 38
Sina Eft
  • 11
  • 1
1

In my case, the problem was that I was sending the state and setstate as props to a child component but there was a typo in setstate

HibaHasan
  • 472
  • 1
  • 3
  • 10