LoginSignup
3
1

【React】初めてのフロントエンド〜ランダムで今日行くラーメン二郎を決めてもらう〜

Posted at

はじめに

ぼく「フロントエンドなんもわからん🤮」
ということでJavaScriptすら怪しい身ですが、Reactで簡単なアプリケーションをつくってみました。

成果物案

  • ボタン押下で東京都・神奈川県のラーメン二郎の店舗情報をランダムで表示する
    • 店舗情報は店舗名・住所・営業時間・定休日とする

作成手順

とりあえずJavaScriptの基礎を知る

Pythonは触ってきたので、JavaScriptの基礎を勉強しました。

Reactの導入

最初はNext.JSのロゴがカッコよくReactの過程をすっ飛ばしてインストールしましたが、冷静に分からなさすぎたので引退しました。

node.jsをインストール後、npx create-react-app app-nameで新規プロジェクトを作成します。

Reactの基礎を知る

以下の動画でReactの基礎を学びました。

【React Hooks入門】完全初心者OK!8種類のHooksを学んでReactの理解を深めよう
新・日本一わかりやすいReact入門【基礎編】

Reactを使用する理由と各フックスの解説を一通り学ぶことができます。

ラーメン屋の情報を取得する

PythonでSeleniumを用いて取得しました。
取得したデータはCSV形式で保存しています。

取得したデータの営業時間や日付の正規化はChatGPTに丸投げです。

Firestroe/Firebaseを知る

これらの動画・記事を参考にしました。

【Firebase】FireStoreに一括でcsvまたはjson形式のデータをアップロードする
【2022最新版】Firebase入門!Reactと連携してデータベース接続をしてみよう

登録から情報の反映方法まで分かりやすく説明しているので、かなり助かりました。

導入後はCSV形式で保存したデータをJsonに変換し、こちらの記事を参考にFirebase/Firestoreにデータを一括登録します。

実際に作成してみる

最低限のCSSを設定して完成です。
CSSはこちらを参考にしました。

CSSで作るローディングアニメーション40選〜待ち時間を楽しくするテクニック

firebase.jsはもろもろの情報があるため、割愛します。

App.js
import { useEffect, useState } from 'react';
import './App.css';
import db from './firebase';
import { collection, getDocs, onSnapshot } from "firebase/firestore";

function App() {
  
  const [posts, setPosts] = useState([]);
  const [selectedPost, setSelectedPost] = useState(null);
  const [isLoading, setIsLoading] = useState(false);

  useEffect( () => {
    const postData = collection(db, "jirouFromTokyoAndKanagawa");
    getDocs(postData).then((snapShot) => {
      const postsData = snapShot.docs.map((doc) => ({ ...doc.data()}));
      setPosts(postsData);
    });

    onSnapshot(postData, (post) => {
      const postsData = post.docs.map((doc) => ({...doc.data()}));
      setPosts(postsData);
    });
  }, [])

  const handleButtonClick = () => {
    if (isLoading) {
      return;
    }

    setIsLoading(true);
    setTimeout(() => {
      if (posts.length > 0) {
        const randomIndex = Math.floor(Math.random() * posts.length);
        setSelectedPost(posts[randomIndex]);
      }
      setIsLoading(false);
    }, 2000);
  }

  return (
    <div className="App">
      <div className="App-header">
        <h1>じろるーれっと</h1>
      </div>
      <div className='mainContaienr'>
        <table className='tableLayout'>
          {isLoading ?
            <div className='circle-body'>
              <div className='spinner-box'>
                <div className="circle-border">
                  <div className="circle-core"></div>
                </div>
              </div>
            </div>:
              selectedPost && (
                <div key={selectedPost.store_name}>
                  <h1 className='title'>{selectedPost.store_name}</h1>
                  <tr>
                    <th>店名</th>
                    <th>{selectedPost.store_name}</th>
                  </tr>
                  <tr>
                    <th>住所</th>
                    <th>{selectedPost.store_address}</th>
                  </tr>
                  <tr>
                    <th>営業時間</th>
                    <th>{selectedPost.open_time}</th>
                  </tr>
                  <tr>
                    <th>定休日</th>
                    <th>{selectedPost.close_day}</th>
                  </tr>
                </div>
              )
            }
        </table>
        <div className='button-container'>
          <div>
            <button onClick={handleButtonClick} className='randomButton'>ラーメン屋を選ぶ</button>
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
App.css
:root {
  --main-background-color: #1e2739;
  --main-color: white;
}

.App, .App-header {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  background-color: var(--main-background-color);
  font-size: calc(10px + 2vmin);
  color: var(--main-color);
}

.App {
  min-height: 100vh;
}

.App-header {
  position: fixed;
  z-index: 999;
  top: 0;
  left: 0;
  width: 100%;
  padding: 20px 40px;
  box-sizing: border-box;
}

.tableLayout, .title {
  text-align: left;
  margin: auto;
}

.mainContaienr {
  width: 75%;
  height: 75%;
}

.randomButton {
  background-color: #00af4f;
  border: none;
  color: var(--main-color);
  text-align: center;
  display: inline-block;
  font-size: 16px;
  font-weight: bold;
  padding: 10px 24px;
  border-radius: 100vh;
  margin: 50px auto 0;
  position: relative;
  transition: background-color 0.4s, transform 0.4s;
  cursor: pointer;
}

.randomButton:hover {
  background-color: #007f3b;
  color: white;
}

.randomButton:active {
	transform: translate(0,10px);
	border-bottom:none;
}

.spinner-box {
  width: 300px;
  height: 300px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: transparent;
}

.circle-border, .circle-core {
  display: flex;
  justify-content: center;
  align-items: center;
  border-radius: 50%;
}

.circle-border {
  width: 150px;
  height: 150px;
  padding: 3px;
  background: linear-gradient(0deg, rgba(63,249,220,0.1) 33%, rgba(63,249,220,1) 100%);
  animation: spin .8s linear 0s infinite;
}

.circle-core {
  width: 100%;
  height: 100%;
  background-color: var(--main-background-color);
}

@keyframes spin {
  from {
      transform: rotate(0);
  }
  to{
      transform: rotate(359deg);
  }
}

.circle-body {
  background-color: var(--main-background-color);
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
  align-items: flex-start;
}

実際に決めてもらう

こんなかんじです。
どういった装飾がよいか思いつかず、ローディングとボタンだけきれいにしました。

React-App-Google-Chrome-2023-06-10-19-38-39-Trim.gif

感想

html, css, javascriptあたりをもう一度復習かな…
特にjavascriptの文法が実際に扱ってみてもまだ理解していない部分が多いので、今後とも学びつつ作成していきます。

次回はこちらで表示した住所をGoogleMapsAPIでMapを表示し、ピンを打つところまでやっていこうかと思います。

3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1