Youtube Search
Create Google Project
- Go to https://console.developers.google.com/ and log in with your Google account
- 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"
- Click ENABLE APIS AND SERVICES at the top of the Dashboard
- Search for "youtube" and click "YouTube Data API v3"
- Click "ENABLE"
- 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
- In the "API key created" window, copy your API key and click "close"
- npm install --save youtube-api-search, install Google youtube API
- 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