自己紹介
G's Academy東京 LAB8期に通っています。
6ヶ月の通いのうち、現在1ヶ月半が経過しました。
今回作ったものは3週目くらいに作成したものです。
2週目に爆死したアプリの記事はこっちに書いてます。
https://poppotennis.com/gs_week2/
完成品はこちら
文字を入力して検索するを押すとGoogleBooksAPIから本の情報の一部を取得し、
お気に入りを押すと、ローカルストレージに本のIDを保存しておいて、それを元にお気に入りを表示するという感じです。
クソコードでも動けば誰かの参考になるので恥晒し覚悟で投稿したいと思います。
使ったファイル
index4.html、index4.js、index4.css
CDNでやっていきます。なぜ4なのかというと、4ページ目だからです。
index4.html
Reactなので必要なCDNを読み込んで、idを指定したdivタグがあれば大丈夫ですね。
axiosとReactの公式ページからCDNをコピーして持ってくるのが正解だと思います。
注意点は、
index4.jsを読み込むときに
<script type="text/babel" src="js/index4.js"></script>
としないと読みこまないこと。type指定をしていなくて無駄に時間を過ごしました。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>my Memo's Pad</title>
<script src="js/jquery-2.1.3.min.js"></script>
<link rel="stylesheet" href="css/reset.css" />
<link
rel="stylesheet"
href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T"
crossorigin="anonymous"
/>
<link
href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/icon?family=Material+Icons"
rel="stylesheet"
/>
<script
src="https://kit.fontawesome.com/02cb9ed103.js"
crossorigin="anonymous"
></script>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/es6-promise@4/dist/es6-promise.auto.min.js"></script>
<link rel="stylesheet" href="css/index4.css" />
</head>
<body>
<!-- ここからコンテンツ -->
<div class="container">
<main>
<div id="index4"></div>
</main>
</div>
<!-- コンテンツここまで -->
<div id="content"></div>
<script
src="https://unpkg.com/react@16/umd/react.development.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"
crossorigin
></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script type="text/babel" src="js/index4.js"></script>
</body>
</html>
楽しいReact.js
('use strict');
const e = React.createElement;
let keyBook = Number(localStorage.getItem('keyBook'));
今回、ローカルストレージを利用するのでkeyにナンバリングしたいのでグローバル変数で現在の何番が保存されているのかを取得しておきました。
親コンポーネント作成
class LikeButton extends React.Component {
constructor(props) {
super(props);
this.state = {
liked: false,
data: [],
inputValue: '',
errorMessage: ''
};
}
//検索で発動する関数
getProfile = async () => {
let datas = [];
let text = this.state.inputValue;
try {
const result = await axios.get(
'https://www.googleapis.com/books/v1/volumes?q=' + text
);
let items = result.data.items;
for (let i = 0; i < items.length; i++) {
let item = items[i];
let authors = item.volumeInfo.authors[0];
let title = item.volumeInfo.title;
let image = item.volumeInfo.imageLinks.thumbnail;
let id = item.id;
let test = { authors, title, image, id };
console.log('test:' + JSON.stringify(test));
datas.push(test);
}
this.setState({
errorMessage: ''
});
} catch (error) {
//ここでリクエストに失敗した時の処理、メッセージを記述します。
this.setState({
errorMessage: 'エラー:該当するものがありませんでした。'
});
}
console.log('data:' + JSON.stringify(datas));
this.setState({
data: datas
});
};
//インプットないの文字を渡しながら着火
onClickSearch = () => {
this.getProfile(this.state.inputValue);
};
//文字を入力されるたびに更新
handleChange = e => {
this.setState({
inputValue: e.target.value
});
};
render() {
return (
<div className='App'>
<div className='index4-left'>
<div className='input-group mb-3'>
<input
type='text'
className='form-control'
placeholder='好きなキーワード'
aria-describedby='basic-addon1'
onChange={this.handleChange}
onKeyPress={event => {
if (event.key === 'Enter') {
this.getProfile();
}
}}
/>
<div className='input-group-prepend'>
<button onClick={this.getProfile} className='btn btn-primary'>
検索する
</button>
</div>
</div>
<p>{this.state.errorMessage}</p>
<div className='search'>
{this.state.data.map(data => {
return (
<div className='card search_image'>
<img
src={data.image}
className='card-img-top'
alt='...'
></img>
<div className='card-body'>
<h5 className='card-title'>{data.title}</h5>
<p className='card-text'>{data.authors}</p>
<AddButton id={data.id} />
</div>
</div>
);
})}
</div>
</div>
<div className='index4-right'>
<FavoliteButton />
</div>
</div>
);
}
}
const domContainer = document.querySelector('#index4');
ReactDOM.render(e(LikeButton), domContainer);
getProfileとonClickSearchでAPIでの取得はできます。
他は備忘録。
お気に入り追加ボタン
親から渡ってきたAPIで取得した本のidをローカルストレージに保存します。
ローカルストレージのkeyは1個保存されるごとに+1されるようにする記述をここでしています。
ローカルストレージにkeyBookというkeyを用意してMySQLでいうAutoIncrementの役割をしてみました。
class AddButton extends React.Component {
constructor(props) {
super(props);
}
clickAddBook = e => {
const addBookKey = keyBook + 1;
localStorage.setItem('keyBook' + addBookKey, e);
localStorage.setItem('keyBook', addBookKey);
keyBook++;
};
clickAddBook2 = () => {
this.clickAddBook(this.props.id);
};
render() {
return (
<button className='btn btn-success' onClick={this.clickAddBook2}>
お気に入りに保存
</button>
);
}
}
お気に入りで登録したものを表示させるコンポーネント
ローカルストレージに保存されているIDを取得して、GoogleBooksAPIからそのIDの本のデータをもらって、Stateに入れるという流れです。
class FavoliteButton extends React.Component {
constructor(props) {
super(props);
this.state = {
errorMessage: '',
datas: []
};
}
getFavorite = async () => {
let items = [];
for (let i = 0; i <= keyBook; i++) {
const item = localStorage.getItem('keyBook' + i);
if (item) {
console.log(item);
//データ取得
try {
//ここでGETメソッドを使用してgithubのプロフィールを取得します。
const result = await axios.get(
'https://www.googleapis.com/books/v1/volumes/' + item
);
let obj = result.data.volumeInfo;
let authors = obj.authors[0];
let title = obj.title;
let image = obj.imageLinks.thumbnail;
let id = obj.id;
let test = { authors, title, image, id };
items.push(test);
this.setState({
errorMessage: ''
});
} catch (error) {
//ここでリクエストに失敗した時の処理、メッセージを記述します。
this.setState({
errorMessage: 'エラー'
});
}
//データ取得ここまで
}
}
this.setState({
datas: items
});
};
render() {
return (
<div className='favorite'>
<button onClick={this.getFavorite} className='btn btn-primary'>
お気に入りを表示
</button>
<div>
{this.state.datas.map(data => {
return (
<div className='media border favorite_content'>
<img src={data.image} className='mr-3'></img>
<div className='media-body'>
<h5>タイトル:{data.title}</h5>
著者:{data.authors}
</div>
</div>
);
})}
</div>
</div>
);
}
}
あとは適当にスタイルをつけて
html,
body {
font-family: 'ヒラギノ明朝 ProN W6', 'HiraMinProN-W6', 'HG明朝E',
'MS P明朝', 'MS PMincho', 'MS 明朝', serif;
height: 100%;
margin: 0;
padding: 0;
}
main {
margin-top: 100px;
margin-bottom: 100px;
}
#nav-drawer {
/* position: relative; */
}
/* アイコンのスペース */
#nav-open {
position: relative;
display: inline-block;
width: 48px;
height: 48px;
vertical-align: middle;
margin-right: 0;
background-color: orange;
}
/* ミッキーの真ん中 */
#nav-open span {
position: absolute;
left: 4px;
bottom: 0;
width: 40px;
height: 40px;
background-color: white;
border-radius: 50%;
display: block;
content: '';
cursor: pointer;
}
/* ミッキーの耳2つの形 */
#nav-open span:before,
#nav-open span:after {
position: absolute;
width: 24px;
height: 24px;
background-color: white;
border-radius: 50%;
display: block;
content: '';
cursor: pointer;
}
/* ミッキーの左耳の位置 */
.App {
display: flex;
justify-content: space-between;
}
.index4-left {
width: 40%;
}
.index4-right {
width: 40%;
}
.search {
text-align: center;
}
.search_image {
/* width: 100%; */
height: 500px;
margin-top: 30px;
}
.favorite {
text-align: center;
}
.favorite_content {
margin-top: 20px;
}
完成です!
苦労した点
axiosに初挑戦した点。
この書き方・・・正しくないだろうなぁ・・・と思いながらとりあえず動かさなければ前に進めない感じ。
徐々に慣れていきたいなぁと思っています。
jsはどのライブラリもフレームワークも面白いです。
参考文献
https://ja.reactjs.org/docs/cdn-links.html
https://developers.google.com/books