20

I have the following component, which maintains the state that gets updated when the event is fired on the an specific element and when the state is updated it is passed down as a prop to another component. I am currently trying why i get the following error "this.setState is not a function", its most likely not binded to the right context. But i am unsure of this, am i doing this right?

export default class SearchBox extends Component{

    constructor(){
        super()
        console.log("search box imported");
        this.state = {
            results:[]
        };
    }

    //this.setState({result: arrayExample})

    searchGif(event) {
        if(event.keyCode == 13){
            let inputVal = this.refs.query.value;
            let xhr = new XMLHttpRequest();
            xhr.open('GET', 'http://api.giphy.com/v1/gifs/search?q='+inputVal+'&api_key=dc6zaTOxFJmzC', true);
            xhr.onreadystatechange = function(){
                if(xhr.readyState == 4 && xhr.status == 200){
                    // this.response = JSON.parse(xhr.responseText);
                    let returnedObj = JSON.parse(xhr.responseText);
                    //console.log(returnedObj.data);
                    let response = returnedObj.data.map(function(record){
                        let reformattedArray =  {   key: record.id,
                                                    id: record.id,
                                                    thumbnailUrl: record.images.fixed_height_small_still.url
                                                };
                                                return reformattedArray;
                    });
                    console.log(response);
                    this.setState({results: response});
                }
            }
            xhr.send();
        }
    }


    render(){

        return(
            <div>   
                <input className="search-input" ref="query" onKeyDown={this.searchGif.bind(this)} name="search" placeholder="Search"/>
                <GifSwatch data={this.state.results} />
            </div>
        );
    }
}

EDIT: I just realized the context gets changed when "onreadyStateChange" function, so i did the following in searchGif

searchGif(){
   //other logic
   var self = this;
   xhr.onreadystatechange = function(){
    //ajax logic
    self.setState({results: repsonse});
   }
}
RRP
  • 2,563
  • 6
  • 29
  • 52
  • `var self = this;` is obsoleted by arrow functions. Since you have ES6 you'd make your indentions clearer by using that. – loganfsmyth Oct 28 '15 at 03:24

1 Answers1

70

You are losing the React class this context. Bind it, and also bind it in the async callback function too.

constructor(props){
    super(props);
    console.log("search box imported");
    this.state = {
        results:[]
    };
    this.searchGif = this.searchGif.bind(this);
}

searchGif(event) {
    // ... code here
    xhr.onreadystatechange = () => {
    // ... code here
        this.setState();
    }
}

awesome thing about arrow functions is they bind your context for you and the syntax is awesome too. downside is browser support. Make sure you have a polyfil or a compile process to compile it into ES5 syntax for cross browser performance.

If you cant do either of those then just make a shadow variable of your this context outside of the async onreadystatechange function and use it instead of this.


Edit

Most compilers these days handle binding methods to the class with arrows (without specifying babel transforms ... etc), you can assign state as well this way without a constructor

export default class SearchBox extends Component {
  state = { 
    results: []
  }
  
  searchGif = (event) => {
    // ... code here
    xhr.onreadystatechange = () => {
    // ... code here
        this.setState();
    }
  }
  render() {
    // ...
  }
}
Community
  • 1
  • 1
John Ruddell
  • 25,283
  • 6
  • 57
  • 86
  • 5
    @RajPowar or you could just use arrow functions which preserve `this` – Brigand Oct 28 '15 at 02:57
  • 2
    no need to bind it if you put `searchGif =(event) =>{ }` – AlainIb Dec 07 '17 at 12:01
  • Assuming you have the correct options for Babel when you compile the code – John Ruddell Dec 07 '17 at 18:38
  • 2
    I'm giving you a belated up vote because this really helped me convert a React component to the ES6 syntax. – Charles Owen Dec 24 '17 at 17:12
  • How is this answer different if the function is imported from say './helpers' ? I'm having trouble getting the context to bind to the imported function which is resulting in the error "this.setState is not a function" – Rodrigo Gomez-Palacio Jun 22 '19 at 19:00
  • Hey Rodrigo, if you want to post a question with your specific setup and ping me if be happy to resolve you issue! I probably wouldn't make a helper function that calls set state unless there was a good reason though. – John Ruddell Jun 22 '19 at 19:04