Youtube Search
Create Google Project
  1. Go to https://console.developers.google.com/ and log in with your Google account
  2. Create a new project
    • On the top of the window, click the downside triangle, which pops up a window that lists all your existing projects
    • Click "NEW PROJECT", name it "YouTube Search", and click "CREATE"
    • Important: Wait a little bit until Google is done creating the project
    • Click the downside triangle again and select the newly created project "YouTube Search"
  3. Click ENABLE APIS AND SERVICES at the top of the Dashboard
  4. Search for "youtube" and click "YouTube Data API v3"
  5. Click "ENABLE"
  6. In the left pane, find "Credentials" and click it. Then click "Credientials in APIs & Services". Click "Create credentials" and select "API key" in the dropdown menu
  7. In the "API key created" window, copy your API key and click "close"
  8. npm install --save youtube-api-search, install Google youtube API
  9. npm install --save bootstrap, install bootstrap
Project Appearance

Project Architecture

Code Organization

  • Whenever any state of a component is changed, the component immediately re-renders and also forces its children components to re-render as well
  • this.setState(...) is the ONLY way to update the React state object
  • In React, the term "downward data flow/one-way data flow/one-way data binding" means that typically only the most parent component (in our project, the App component) should be responsible for fetching the data from either an API server or a Redux framework and saving them in the form of states
  • //index.js
    import React from 'react';
    import ReactDOM from 'react-dom';
    import YTSearch from 'youtube-api-search';
    import _ from 'lodash';
    import 'bootstrap/dist/css/bootstrap.min.css'; // import Bootstrap CSS library
    import "./index.css"; // in addition, import our own CSS specs
    import SearchBar from "./components/search_bar";
    import VideoList from "./components/video_list";
    import VideoDetail from "./components/video_detail";
    
    const API_KEY = 'AIzaSyCf0m-NsXkkwEcdlQ2072ESd4NQvJsgeoY';
    
    class App extends React.Component {
        constructor(props) {
            super(props);
    
    	this.state = { videos: [], selectedVideo: null };
        }
    	    
        videoSearch(term) {
            YTSearch({ key: API_KEY, term: term }, videos => {
                // ES6 syntax for this.setState({ videos: videos});
                this.setState({
                    videos: videos,
                    selectedVideo: videos[0]
                });
            });
        }
    
        render() {
    	const videoSearch = _.debounce((term) => { this.videoSearch(term) }, 500);
            return (
                <div>
                    <SearchBar onSearchTermSearch={videoSearch}/>
    		<div className="row">
    			<VideoDetail video={this.state.selectedVideo} />
    			<VideoList // callback function
                            onVideoSelect={selectedVideo =>
                                this.setState({ selectedVideo })
                            }
    			videos={this.state.videos} />
    		</div>
                </div>
            );
        }
    }
    
    ReactDOM.render(<App />, document.getElementById('root'));
    		
    //index.css
    body {
      margin: 0;
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
        "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
        sans-serif;
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
    }
    
    .row {
        margin: 20px;
    }
    
    .search-bar {
        margin: 20px;
        text-align: center;
    }
    
    .search-bar input {
        width: 75%;
    }
    
    .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;
    }
    
    code {
      font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
        monospace;
    }
    		
    //video_list.js
    import React from "react";
    import VideoListItem from "./video_list_item";
    
    const VideoList = props => {
        const videoIterms = props.videos.map(video => {
            return <VideoListItem onVideoSelect={props.onVideoSelect}
                    key={video.etag} video={video} />;
        });
    
        return <ul className="col-md-4 list-group">{ videoIterms }</ul>;
    };
    
    export default VideoList;
    		
    //video_list_item.js
    import React from 'react';
    
    const VideoListItem = ({video, onVideoSelect}) => {
        const imageUrl = video.snippet.thumbnails.default.url;
        const title = video.snippet.title;
    
        return (
            <li onClick={ () => onVideoSelect(video) } className="list-group-item">
                <div className="video-list media">
                    <div className="media-body">
                        <div className="media-heading">{title}</div>
                    </div>
                    <img className="media-object" src={imageUrl} alt="" />
                </div>
            </li>
        );
    }
    
    export default VideoListItem;
    		
    //video_detail.js
    import React from "react";
    
    const VideoDetail = ({ video }) => {
        if(!video){
            return <div>Loading...</div>;
        }
    
        const videoId = video.id.videoId;
        // ES6 syntax for
        // const url = 'https://www.youtube.com/embed/' + 'videoId';
        const url = `https://www.youtube.com/embed/${videoId}`;
    
        return (
            <div className="video-detail col-md-8">
                <div className="embed-responsive embed-responsive-16by9">
                    <div>
                        <iframe className="embed-responsive-item" src={url} title={video.snippet.title}/>
                    </div>
                </div>
                <div className="details">
                    <div>{video.snippet.title}</div>
                    <div>{video.snippet.description}</div>
                </div>
            </div>
        );
    };
    
    export default VideoDetail;
    
    		
    //search_bar.js
    import React, { Component } from 'react';
    
    class SearchBar extends React.Component {
       constructor(props) {
            super(props);
    
            this.state = { term: "" };
        }
    
        onInputChange(term) {
            this.setState({term});
            this.props.onSearchTermSearch({term});
        }
    
        render() {
            return (
                <div className="search-bar">
                    <input
    		    value={this.state.term} //Controlled Component
    		onChange={event => this.onInputChange(event.target.value)}
                    />
                </div>
            );
        }
    }
    
    export default SearchBar;
    		
    Project Domain
    https://youtube-search-lin.herokuapp.com/
    		
    Reference
  • CS4840 by Dr. Zhiguang Xu at Valdosta State University