環境の準備
①ターミナルでreactアプリケーションを作成する。
$ npx create-react-app <プロジェクト名>
% cd <プロジェクト名>
% npm start
② 必要なパッケージをインストールする。
- 公式サイト:Tailwind CSS
$ 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
API keysを作成する。
3.公式サイト:Moment.js
③ 不必要なファイルを削除する。
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)}°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)}°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)}°C
</p>
<p className="text-gray-400 text-lg">
Max Temp:{Math.round(main.temp_max)}°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)}°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