Sunday, August 28, 2016

2. Ajax Requests with React

Youtube Search Response

Need to make a call to youtube api to get information.

Which component should retrieve the list of videos? Downwards data flow: the most parent component should fetch the data.

index is the most parent component we have.

import React from 'react';
import ReactDOM from 'react-dom';
import SearchBar from './components/search_bar'; 
import YTSearch from 'youtube-api-search';

const API_KEY = 'AIzaSyC2AOEumcukxg2H2E5TOYBfeV5--GWOeyU';

YTSearch({key: API_KEY, term: 'surfboards'},function(data){
    // call back function
    console.log(data);

    // check console.
});

const App = () => {
    return (    
        <div>
            <SearchBar />
        </div>
    );
}

ReactDOM.render(<App />,document.querySelector('.container'));

Refactoring Functional Components to Class Components

const API_KEY = 'AIzaSyC2AOEumcukxg2H2E5TOYBfeV5--GWOeyU';

class App extends Component {

    constructor(props){
        super(props);

        this.state = {videos: []}; // array of videos

        YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {    
            this.setState({ videos });
            // this.setState({videos: videos});
        });        
    }

    render(){
        return (    
            <div>
                <SearchBar />
            </div>
        );
    }
}

ReactDOM.render(<App />,document.querySelector('.container'));

Props

Will focus on list of videos

video_list.js

import React from 'react';

// add some classnames to add some styling
// add boostrap link in index.html

const VideoList = (props) => {    
    return (
        <ul className="col-md-4 list-group">
            {props.videos.length}
        </ul>
    );
}

export default VideoList;

in index.js

import VideoList from './components/video_list';

    // passing props
    render(){
        return (    
            <div>
                <SearchBar />                
                <VideoList videos={this.state.videos} />
            </div>
        );

    }

Building Lists with Map

video_list.js

import React from 'react';
import VideoListItem from './video_list_item'

const VideoList = (props) => {
    const videoItems = props.videos.map((video) => {
        return <VideoListItem video={video} />
    });

    return (
        <ul className="col-md-4 list-group">
            {videoItems}
        </ul>
    );
};

export default VideoList;

/*
map example

var array = [1,2,3];

array.map((number) => {return number * 2})
array.map((number) => {return '<div>' + number + '</div>'})
 */

video_list_item.js

import React from 'react'

const VideoListItem = (props) => {
    return <li>Video</li>
};


export default VideoListItem;

List item keys

in video_list.js add. Improve search efficiency

return <VideoListItem key={video.etag} video={video} />

Video List Items

video_list_item.js


import React from 'react'

const VideoListItem = ({video}) => {  
    const imageUrl = video.snippet.thumbnails.default.url;


    return (
        <li className="list-group-item">
            <div className='video-list media'>
                <div className="media-left">
                    <img className="media-object" src={imageUrl}/>
                </div>

                <div className="media-body">
                    <div className="media-heading">{video.snippet.title}</div>
                </div>
            </div>
        </li>
    )
};


export default VideoListItem;

Detail Component and Template Strings

Video Selection

index.js

class App extends Component {

    constructor(props){
        super(props);

        this.state = {
            videos: [],
            selectedVideo: null
        }; 

        YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {
            // when state is set, render will be called again    
            this.setState({ 
                videos: videos,
                selectedVideo: videos[0]
            });            
        });        
    }

    // passing props
    render(){
        return (    
            <div>
                <SearchBar />
                <VideoDetail video={this.state.selectedVideo}/>                
                <VideoList 
                    onVideoSelect={selectedVideo => this.setState({selectedVideo})}
                    videos={this.state.videos} />
            </div>
        );
    }
}

ReactDOM.render(<App />,document.querySelector('.container'));


define a function that updates app state. takes a video and updates the selected video
    pass this as a property 'onVideoSelect' into videolist

video_list.js

const VideoList = (props) => {
    const videoItems = props.videos.map((video) => {
        return (
          <VideoListItem
            onVideoSelect={props.onVideoSelect} 
            key={video.etag} 
            video={video} />
        )
    });

    return (
        <ul className="col-md-4 list-group">
            {videoItems}
        </ul>
    );
};

export default VideoList;

videolist takes the onVideoSelect property, and pass it into videolistitem

video_list_item.js

import React from 'react'

const VideoListItem = ({video, onVideoSelect}) => {

    const imageUrl = video.snippet.thumbnails.default.url;

    return (
        <li onClick={() => onVideoSelect(video)} className="list-group-item">
            <div className='video-list media'>
                <div className="media-left">
                    <img className="media-object" src={imageUrl}/>
                </div>

                <div className="media-body">
                    <div className="media-heading">{video.snippet.title}</div>
                </div>
            </div>
        </li>
    )
};

export default VideoListItem;

videolistitem takes the onVideoSelect property and says, 
whenever i am clicked, call that function with the video that i was passed.
Such a call back shouldn't be more than two level deep'
it can get confusing from index.js all the way to video_list_item

Styling with CSS

style.css

.search-bar{
    margin: 20px;
    text-align: center;
}

.video-item input{
    width: 64px;
}

.video-detail .details{
    margin-top: 10px;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

.list-group-item{
    cursor: pointer;
}

.list-group-item:hover{
    background-color:#eee;

}

Make sure in search_bar.js, you have

            <div className="search-bar">

Searching for videos

Add ability to search for new videos. 

class App extends Component {

    constructor(props){
        super(props);

        this.state = {
            videos: [],
            selectedVideo: null
        };   

        this.videoSearch('surfboards');     
    }

    // passing props
    render(){
        return (    
            <div>
                <SearchBar onSearchTermChange={(term => this.videoSearch(term))} />
                <VideoDetail video={this.state.selectedVideo}/>                
                <VideoList 
                    onVideoSelect={selectedVideo => this.setState({selectedVideo})}
                    videos={this.state.videos} />
            </div>
        );
    }

    videoSearch(term){
        YTSearch({key: API_KEY, term: term}, (videos) => {
            // when state is set, render will be called again    
            this.setState({ 
                videos: videos,
                selectedVideo: videos[0]
            });            
        }); 
    }    
}

ReactDOM.render(<App />,document.querySelector('.container'));


Refactored youtube search in its own method, VideoSearch. Passed this method onto SearchBar under the property 
onSearchTermChange. So Searchbar has to call props.onSearchTermChange.
, pass in a function, will call videoSearch with term.
When onSearchTermChange is called...

search_bar.js

import React from 'react';

class SearchBar extends React.Component{

    constructor(props){
        super(props);
        this.state = {term: 'Starting Value'};
    }

    render(){     
        return (
            <div className="search-bar">                 
                <input
                    value = {this.state.term} 
                    onChange={event => this.onInputChange(event.target.value)}
                />       
            </div>
        )
    }

    onInputChange(term){
        this.setState({term});
        // fires off the callback function onsearchtermchange.
        this.props.onSearchTermChange(term);
    }
}

export default SearchBar;


Throttling Search Term Input

Its now laggy because search is called whenever we type something. UPdates are too often and overwhelming for users. WIll be good if we can throttlng.

1. need to install lodash

in terminal, 
npm install --save lodash

2.

import _ from 'lodash';

3.
    render(){    
        const videoSearch = _.debounce((term) => {this.videoSearch(term)},300);

        return (    
            <div>
                <SearchBar onSearchTermChange={videoSearch} />
                <VideoDetail video={this.state.selectedVideo}/>                
                <VideoList 
                    onVideoSelect={selectedVideo => this.setState({selectedVideo})}
                    videos={this.state.videos} />
            </div>
        );
    }

want to call function once every ... milliseconds
we created a fat arrow function here and passed it to debounce. Debounce takes this inner function and returns a new function
that can only be called once every 300 milliseconds. 
We then pass it into SearchBar.

No comments:

Post a Comment