React.jsでGoogleBooksAPIを取り込んでローカルストレージにお気に入りを登録できるやつ作った

#自己紹介
G's Academy東京　LAB8期に通っています。
６ヶ月の通いのうち、現在１ヶ月半が経過しました。
今回作ったものは３週目くらいに作成したものです。

２週目に爆死したアプリの記事はこっちに書いてます。
https://poppotennis.com/gs_week2/

#完成品はこちら
<img width="1409" alt="スクリーンショット 2019-11-24 2.35.29.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/447711/4b0b9fa1-e6a2-500b-0f1a-86eae3be04a1.png">

文字を入力して検索するを押すとGoogleBooksAPIから本の情報の一部を取得し、
お気に入りを押すと、ローカルストレージに本のIDを保存しておいて、それを元にお気に入りを表示するという感じです。

クソコードでも動けば誰かの参考になるので恥晒し覚悟で投稿したいと思います。

#使ったファイル

index4.html、index4.js、index4.css
CDNでやっていきます。なぜ４なのかというと、４ページ目だからです。

#index4.html

Reactなので必要なCDNを読み込んで、idを指定したdivタグがあれば大丈夫ですね。
axiosとReactの公式ページからCDNをコピーして持ってくるのが正解だと思います。
注意点は、
####index4.jsを読み込むときに

```
<script type="text/babel" src="js/index4.js"></script>
```

としないと読みこまないこと。type指定をしていなくて無駄に時間を過ごしました。

```index4.html

<!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>
```
https://ja.reactjs.org/docs/cdn-links.html

#楽しいReact.js

```index4.js
('use strict');

const e = React.createElement;
let keyBook = Number(localStorage.getItem('keyBook'));
```
今回、ローカルストレージを利用するのでkeyにナンバリングしたいのでグローバル変数で現在の何番が保存されているのかを取得しておきました。

###親コンポーネント作成

```index4.js
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個保存されるごとに＋１されるようにする記述をここでしています。
ローカルストレージにkeyBookというkeyを用意してMySQLでいうAutoIncrementの役割をしてみました。

```index4.js
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に入れるという流れです。

```index4.js
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>
    );
  }
}
```

あとは適当にスタイルをつけて

```index4.css
html,
body {
  font-family: 'ヒラギノ明朝 ProN W6', 'HiraMinProN-W6', 'HG明朝E',
    'ＭＳ Ｐ明朝', '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;
}

/* ミッキーの耳２つの形 */
#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