React でアプリを作成しました【14】【Weather app Using Tailwind CSS】

## 環境の準備

①ターミナルでreactアプリケーションを作成する。

```
$ npx create-react-app <プロジェクト名>
% cd <プロジェクト名>
% npm start
```

② 必要なパッケージをインストールする。

1. 公式サイト:[Tailwind CSS](https://tailwindcss.com/)

```
$ npm install -D tailwindcss postcss autoprefixer
$ npx tailwindcss init -p
$ npm install moment --save
```

```src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;
```

```postcss.config.js
module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}
```

```tailwind.config.js
module.exports = {
  content: ["./src/**/*.{js,jsx,ts,tsx}"],
  theme: {
    extend: {},
  },
  plugins: [],
};
```

2.公式サイト:[Open Weather](https://home.openweathermap.org/api_keys)

API keysを作成する。

3.公式サイト:[Moment.js](https://momentjs.com/)

③ 不必要なファイルを削除する。

App.css
App.test.js
logo.svg
reportWebVitals.js
setupTests.js

## コンポーネント・ファイル構成

```
 src
  ├─ assets //画像を入れる
  ├─ components
       ├── DetailCard.js
       ├── Header.js
       └── SummaryCard.js
  ├── App.js
  ├── index.css
  └── index.js
 ├── .env
 ├── craco.config.js
 ├── tailwind.config.js
```

```.env
REACT_APP_API_KEY = "our API_KEY"
REACT_APP_URL = https://api.openweathermap.org/data/2.5/forecast?
REACT_APP_ICON_URL = http://api/openweathermap.org/img/wn/
```
```public/index.html

// 省略

 <link
      rel="stylesheet"
      href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"
    />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>

//省略
```

```src/components/DetailCard.js
import moment from "moment";

function DetailCard({ weather_icon, data }) {
  const { clouds, main, weather } = data.list[0];
  return (
    <div className="container p-4 flex items-center justify-center shadow-lg roundedfd-lg bg-white h-1/3 mb-auto">
      <div className="my-auto">
        <p className="font-bold text-5xl text-pink-800 mb-2">
          {Math.round(main.temp)}&deg;C
        </p>
        <p className="text-4xl text-gray-800 tracking-widest">
          {weather[0].main}
          <img src={weather_icon} className="w-1/4 inline" />
        </p>
        <p className="text-gray-400 text-xs uppercase tracking-widest">
          {weather[0].description}
        </p>
        <p className="tracking-wider">{moment().format("dddd MMM YYYY")}</p>
      </div>
      <div className="my-2 border-l-2 border-gray-100 p-2">
        <p className="text-gray-400 text-lg">
          RealFeel:{Math.round(main.feel_like)}&deg;C
        </p>
        <p className="text-gray-400 text-lg">Humidity:{main.humidity}%</p>
        <p className="text-gray-400 text-lg">Cloud Cover:{clouds.all}%</p>
        <p className="text-gray-400 text-lg">
          Min Temp:{Math.round(main.temp_min)}&deg;C
        </p>
        <p className="text-gray-400 text-lg">
          Max Temp:{Math.round(main.temp_max)}&deg;C
        </p>
      </div>
    </div>
  );
}

export default DetailCard;
```

```src/components/Header.js
const Header = () => {
  return (
    <ul className="flex ml-auto w-full font-bold">
      <li className="text-xs text-gray-800 ml-auto mr-6 border-b-2 border-green-400 cursor-pointer">
        Weather
      </li>
      <li className="text-xs text-gray-800 mr-6 alert-notice cursor-pointer border-b-2 hover:border-green-400">
        Alerts
      </li>
      <li className="text-xs text-gray-800 mr-6 cursor-pointer border-b-2 hover:border-green-400">
        Map
      </li>
      <li className="text-xs text-gray-800 mr-6 cursor-pointer border-b-2 hover:border-green-400">
        Statelite
      </li>
      <li className="text-xs text-gray-800 cursor-pointer border-b-2 hover:border-green-400">
        News
      </li>
    </ul>
  );
};

export default Header;
```

```src/components/SummaryCard.js
import moment from "moment";

function SummaryCard({ day }) {
  let day_icon = `${
    process.env.REACT_APP_ICON_URL + day.weather[0]["icon"]
  }@2x.png`;
  return (
    <li className="container p-4 flexc items-center justify-center bg-gray-200 rounded-lg my-auto mr-1">
      <div className="my-auto">
        <p className="font-bgold text-3xl text-ping-600 mb-2">
          {Math.round(day.main.temp)}&deg;C
        </p>
        <p className="text-2xl text-gray-800 tracking-widest">
          {day.weather[0].main}
          <img src={day_icon} className="w-1/4 inline" />
        </p>
        <p className="text-gray-400 text-xs uppercase tracking-widest">
          {day.weather[0].description}
        </p>
        <p className="tracking-wider">
          {moment(day.dt_txt).format("dddd hh:mm")}am
        </p>
      </div>
    </li>
  );
}

export default SummaryCard;
```
```src/App.js
import { useState } from "react";
import DetailCard from "./components/DetailCard";
import Header from "./components/Header";
import SummaryCard from "./components/SummaryCard";

function App() {
  const API_KEY = process.env.REACT_APP_API_KEY;

  const [noData, setNoData] = useState("No Data Yet");
  const [searchTerm, setSearchTerm] = useState("");
  const [weatherData, setWeatherData] = useState([]);
  const [city, setCity] = useState("Unkown location");
  const [weatherIcon, setWeatherIcon] = useState(
    `${process.env.REACT_APP_ICON_URL}10n@2x.png`
  );

  const handleSubmit = (e) => {
    e.preventDefault();
    getWeather(searchTerm);
  };
  const handleChange = (input) => {
    const { value } = input.target;
    setSearchTerm(value);
  };
  const getWeather = async (location) => {
    setWeatherData([]);

    let how_to_search =
      typeof location === "string"
        ? `q=${location}`
        : `lat=${location[0]}&lon=${location[1]}`;

    try {
      let res = await fetch(
        `${
          process.env.REACT_APP_URL + how_to_search
        }&appid=${API_KEY}&units=metric&cnt=5&exclude=hourly,minutely`
      );
      let data = await res.json();

      if (data.cod !== 200) {
        setNoData("Location Not Found");
        return;
      }
      setWeatherData(data);
      setCity(`${data.city.name},${data.city.country}`);
      setWeatherIcon(
        `${process.env.REACT_APP_ICON_URL + data.list[0]["icon"]}#4x.png`
      );
    } catch (error) {
      console.log("Error encounterd:" + error);
    }
  };

  const myIP = (location) => {
    const { latitude, longitude } = location.coords;
    getWeather([latitude, longitude]);
  };

  return (
    <div className="bg-gray-800 flex items-center justify-center w-screen h-screen py-10">
      <div className="flex w-3/4 min-h-full rounded-3xl shadow-lg m-auto bg-gray-100">
        {/* from card section */}
        <div className="form-container">
          <div className="flex items-center justify-center">
            <h3 className="my-auto mr-auto text-xl text-pink-800 font-bold shadow-md py-1 px-3 rounded-md bg-white bg-opacity-30">
              forecast
            </h3>
            <div className="flex p-2 text-gray-100 bg-gray-600 bg-opacity-30 rounded-lg">
              <i className="fa fa-map my-auto" aria-hidden="true"></i>
              <div className="text-right">
                <p className="font-semibold text-sm ml-2">{city}</p>
              </div>
            </div>
          </div>
          <div className="flex flex-col items-center justify-center h-hull">
            <h1 className="text-white text-2xl">
              The Only Weather Forecast App You Need
            </h1>
            <hr className="h-1 bg-white w-1/4 rounded-full my-5" />
            <form
              noValidate
              onSubmit={handleSubmit}
              className="flex justify-center w-full"
            >
              <input
                type="text"
                className="relative rounded-xl py-2 px-3 w-2/3 bg-gray-300 bg-opacity-60 text-white placeholder-gray-200"
                onChange={handleChange}
                required
              />
              <button type="submit" className="z-10">
                <i
                  className="fa fa-search text-white ml-10 border-l my-auto z-10 cursor-pointer p-3"
                  aria-hidden="true"
                  onClick={() => {
                    navigator.geolocation.getCurrentPosition(myIP);
                  }}
                ></i>
              </button>
            </form>
          </div>
        </div>
        {/*info card section */}
        <div className="w-2/4 p-5">
          <Header />
          <div className="flex flex-col my-10">
            {weatherData.length === 0 ? (
              <div className="container p-4 flex items-center justify-center h-1/3 mb-auto">
                <h1 className="text-gray-300 text-4xl font-bold uppercase">
                  {noData}
                </h1>
              </div>
            ) : (
              <>
                <h1 className="text-5xl text-gray-800 mt-auto mb-4">Today</h1>
                <DetailCard weather_icon={weatherIcon} data={weatherData} />
                <h1 className="text-3xl text-gray-600 mb-4 mt-10">
                  More On{city}
                </h1>
                <ul className="grid grid-cols-2 gap-2">
                  {weatherData.list.map((days, index) => {
                    if (index > 0) {
                      return <SummaryCard key={index} day={days} />;
                    }
                  })}
                </ul>
              </>
            )}
          </div>
        </div>
      </div>
    </div>
  );
}

export default App;
```

```src/index.css
@tailwind base;
@tailwind components;
@tailwind utilities;

@import url("https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;700&display=swap");

body {
  font-family: "Open sans", sans-serif;
  font-weight: 300;
}

h1 {
  font-weight: bold;
}
.form-container {
  background: url("./assets/blue.jpg") no-repeat 50% 40% / cover;
  height: auto;
  @apply w-2/4 p-5 rounded-3xl;
}
.alert-notice::after {
  content: "";
  width: 5px;
  height: 5px;
  position: absolute;
  border-radius: 50;
  margin-left: 2px;
  margin-top: -3px;
  @apply animate-ping bg-pink-800;
}
```

```src/index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById("root")
);
```




## 参考サイト
[Build a Weather Forecast App in React and Tailwind for Beginners 2021](https://www.youtube.com/watch?v=Wj208SZ1SIU&t=11s)