Reagir this.setState não é uma função

288

Eu sou novo no React e estou tentando escrever um aplicativo trabalhando com uma API. Eu continuo recebendo esse erro:

TypeError: this.setState não é uma função

quando tento lidar com a resposta da API. Eu suspeito que há algo errado com essa ligação, mas não consigo descobrir como corrigi-la. Aqui está o código do meu componente:

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>
        );
    }
});
Ivan
fonte

Respostas:

348

O retorno de chamada é feito em um contexto diferente. Você precisa binddethis a fim de ter acesso ao interior do 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: Parece que você tem que ligar os dois inite apichama:

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
fonte
@TravisReeder, não. Não há menção de ligação no tutorial.
Tor Haugen
8
Provavelmente houve 2,5 anos atrás. Tra
Travis Reeder
1
Eu usei a função de seta para resolver o problema. Obrigado pela ajuda
Tarun Nagpal
Alguém pode aprofundar o significado de "retorno de chamada feito em um contexto diferente"?
SB
135

Você pode evitar a necessidade de .bind (this) com uma função de seta ES6.

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
fonte
1
Isso funciona bem. De fato, a palavra-chave da função não deve ser mostrada em um arquivo es6.
JChen___
6
Sua resposta me ajudou :-) Usando uma classe ES6 e RN 0.34, encontrei duas maneiras de vincular "this" a uma função de retorno de chamada. 1) onChange={(checked) => this.toggleCheckbox()}, 2) onChange={this.toggleCheckbox.bind(this)}.
devdanke
Isso é bom, desde que você não precise oferecer suporte a navegadores antigos.
Usuário
Solução perfeita
Hitesh Sahu
2
GMsoF, essas duas soluções funcionam porque a) quando você o faz .bind(this), define o valor thispara o contexto pai de onde this.toggleCheckbox()é chamado; caso contrário, thisse refere ao local onde foi realmente executado. b) A solução de seta gorda funciona porque retém o valor de this, portanto, está ajudando você a não alterar violentamente o valor de this. Em JavaScript, thissimplesmente se refere ao escopo atual; portanto, se você escreve uma função, thisessa é a função. Se você colocar uma função dentro dela, thisestá dentro dessa função filho. Flechas de gordura manter o contexto de onde chamou de
agm1984
37

você também pode salvar uma referência thisantes de invocar o apimétodo:

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
fonte
32

A React recomenda vincular isso em todos os métodos que precisam usá-lo, em classvez disso, de si mesmo function.

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

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

Ou você pode usar em seu arrow functionlugar.

uruapanmexicansong
fonte
14

Você só precisa vincular seu evento

por exemplo

// 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
fonte
10

Agora o ES6 tem a função de seta, é realmente útil se você realmente confundir com a expressão bind (this), você pode tentar a função de seta

É assim que eu faço.

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
fonte
8

Você não precisa atribuir isso a uma variável local se usar a função de seta. As funções de seta são vinculadas automaticamente e você pode evitar problemas relacionados ao escopo.

O código abaixo explica como usar a função de seta em diferentes cenários

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
fonte
5

Agora, em reagir com o es6 / 7, você pode vincular a função ao contexto atual com a função de seta como esta, fazer solicitações e resolver promessas como esta:

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

Com esse método, você pode facilmente chamar essa função no componentDidMount e aguardar os dados antes de renderizar seu html na função de renderização.

Não sei o tamanho do seu projeto, mas pessoalmente aconselho a não usar o estado atual do componente para manipular dados. Você deve usar um estado externo como Redux ou Flux ou algo mais para isso.

Quentin Malguy
fonte
5

use as funções de seta, pois as funções de seta apontam para o escopo pai e isso estará disponível. (substituto da técnica de ligação)

Pranav Kapoor
fonte
1
Uma ótima solução sucinta
Chun
tendo o mesmo problema, embora usado para chamar o método como uma função de seta na chamada, mas acho que tenho que implementar a função de estado como tal e exatamente o que fiz
Carmine Tambascia
3

Aqui, este contexto está sendo alterado. Use a função de seta para manter o contexto da classe React.

        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
fonte
1

Se você está fazendo isso e ainda está tendo um problema, meu problema é que eu estava chamando duas variáveis ​​com o mesmo nome.

Eu tinha companiescomo objeto trazido do Firebase e estava tentando ligar this.setState({companies: companies})- não estava funcionando por razões óbvias.

LukeVenter
fonte