0

I'm just starting out in React and am building an app that incorporates a video search through Youtube's API. I am trying to dynamically render video data in my "search-results" div after querying the API for videos.

This "search-results" div is supposed to render an array of JSX, titled searchResults, that is stored as a state key-value pair of the AddSong component. Upon component construction, this array is empty, and it is supposed to be populated in the .then(function(response){} function of the axios get request (in my search function).

In this function, I am extracting relevant data from the API response item by item. For each item, I format data into JSX, and store it into a local array titled vidComponents. My problem arises when I call this.setState({searchResults: vidComponents}). 'This' is undefined. The exact error is stated below:

TypeError: Cannot read property 'setState' of undefined

After doing some research, I learned that most people get this error due to not binding 'this' to relevant functions correctly in the constructor, but this isn't the case for me. 'This' is bound to my search function in the constructor as follows:

this.search = this.search.bind(this)

Does anyone know what is causing this error? Here's my code:

import React, { Component } from 'react';
import axios from 'axios';
import { FormGroup, FormControl, Button } from 'react-bootstrap';
import './add-song-component.css';

class AddSong extends React.Component{
    constructor(props, context){
        super(props, context);

        // Must bind this to functions in order for it to be accessible within them
        this.handleTextChange = this.handleTextChange.bind(this);
        this.search = this.search.bind(this);

        // Object containing state variables
        this.state = {
            searchText: '',
            searchResults: [],
            nextPageToken: ''
        };
    }

    // Keeps track of the current string in the search bar
    handleTextChange(e){
        this.setState({ searchText: e.target.value });
    }

    // Runs a search for 10 youtube videos through youtube's API
    search(e){
        // Initialize important data for formatting get request url
        var api = this.props.apiKey;
        var query = this.state.searchText;
        var maxResults = 10;

        // Execute get request on Youtube's search API
        axios.get('https://www.googleapis.com/youtube/v3/search', {
            params: {
                part: 'snippet',
                type: 'video',
                order: 'viewCount',
                key: api,
                q: query,
                maxResults: maxResults
            }
        })
        .then(function (response) {
            console.log(response.data);

            var vidComponents = [];

            // Loop through video data, extract relevant info
            var i = 0;
            for (let vidInfo of response.data.items) {
                vidComponents.push(
                    <div key={i}>
                        <p>title: {vidInfo.snippet.title}</p>
                        <p>channel: {vidInfo.snippet.channelTitle}</p>
                        <p>id: {vidInfo.id.videoId}</p>
                        <p>thumbnailUrl: {vidInfo.snippet.thumbnails.high.url}</p>
                    </div>
                );
                i++;
            }

            // Set searchResults state to account for new results...
            // Set nextPageToken state based on value from youtube's response
            // *** used for retrieving more videos from initial query ***
            this.setState({
                searchResults: vidComponents,
                nextPageToken: response.data.nextPageToken
            });
        })
        .catch(function (error) {
            console.log(error);
        }); 
    }

    render(){
        return(
            <div className="add-song">
                <form>
                    <FormGroup controlId="SearchText">
                        <FormControl 
                            type="text"
                            placeholder="Search for a song / video"
                            value={this.state.searchText}
                            onChange={this.handleTextChange}
                        />
                    </FormGroup>
                    <Button onClick={this.search}>Search</Button>
                </form>

                <div className="search-results">
                    {this.state.searchResults}
                </div>
            </div>
        );
    }
}

export default AddSong; 
6
  • Did you debug your code? Commented Mar 16, 2018 at 4:56
  • issue is inside axios .then method, you need to bind that to access this inside, like this: .then((response) => {....} Commented Mar 16, 2018 at 5:02
  • this in an Event refers to the Object to which the Event belongs. In other words, the Element. Commented Mar 16, 2018 at 5:03
  • Possible duplicate of how to do setState inside callback Commented Mar 16, 2018 at 5:05
  • You were right, Mayank. Thank you! Commented Mar 16, 2018 at 6:22

3 Answers 3

1

IMHO both your search and handleSearch functions are scoped well in the constructor. That shouldn't be a problem . But i do see a setState inside your search func:

axios.get(...).then(function() { this.setState(...) }

It would be better off if you can use an arrow function instead !

axios.get(...).then(() => { this.setState(...) }

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

Comments

0
handleTextChange = e => {
    this.setState({ searchText: e.target.value });
}

This syntax will bind this to the current class. While in your case the this will point to the current function scope so you are getting undefined

1 Comment

no issue with that code, issue is in this line: .then(function (response) {, OP forgot to bind the axios .then method. it should be .then((response) => {
-1

you are missing this binding to your handleTextChange function.So,its not invoked with Component this object.

solution1 : using binding inside of constructor

constructor(props, context){
        super(props, context);
       //...
       this. handleTextChange = this.handleTextChange.bind(this);
}

Solution2 : using arrow function.

handleTextChange =(e)=>{
        this.setState({ searchText: e.target.value });
    }

3 Comments

No, that's not why. Look at where this.setState is scoped
@Andrew yes its scoped to AddSong Component this.but while invoking . handleTextChange its not get bind to the component this
@RIYAJKHAN, no issue with that code, issue is in this line: .then(function (response) {, OP forgot to bind the axios .then method. it should be .then((response) => {..}

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.