React + Tailwind CSS+APIで天気アプリケーション作成
ReactとTailwind CSSフレームワークを使い天気のアプリケーション作成を通してReact Hooks、Fetch関数、propsなどのReactの基本的な使い方を紹介しています。
React.js環境にCSSフレームワークのTailwind CSSの設定を行った後、Tailwind CSSで作成したWeatherコンポーネントを作成します。作成したWeatherコンポーネントからAPI経由でOpenWeatherサービスから天気情報を取得して、4つの都市の天気情報をブラウザ上に表示させます。
Reactの基本的な知識のみで作成することができるのでReact入門者の人向けの文書になっています。
天気アプリケーションを作成する中で以下のことを学ぶことができます。
- React環境でのTailwind CSSの設定方法
- Tailwind CSSの使い方
- APIを使った天気情報の取得方法(OpenWeather)
- Reach Hooksの使い方(useState, useEffect)
- コンポーネントへのpropsの渡し方
目次
Reactの環境構築
Reactプロジェクトの環境を構築するためにはnode.jsがインストールされている必要があります。node -vコマンドを実行してバージョンが表示されない場合はnode.jsのインストールを行ってからReact環境の構築を行ってください。
Viteを利用してReactのプロジェクトを作成します。”npm create vite@latest”コマンドを実行するとプロジェクト名とFrameworkとvariantを聞かれます。ここではプロジェクト名にtailwindcss_weather, FrameworkにReact, VariantでJavaScriptを選択しています。
% npm create vite@latest
> npx
> create-vite
✔ Project name: … tailwindcss_weather
✔ Select a framework: › React
✔ Select a variant: › JavaScript
Scaffolding project in /Users/mac/Desktop/tailwindcss_weather...
Done. Now run:
cd tailwindcss_weather
npm install
npm run dev
プロジェクト作成後、プロジェクトフォルダに移動してnpm installコマンドを実行してJavaScriptライブラリのインストールを行います。
% cd tailwindcss_weather
% npm install
インストールが完了後、npm run devコマンドで開発サーバを起動します。
% npm run dev
> tailwindcss_weather@0.0.0 dev
> vite
VITE v5.3.4 ready in 679 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h +
開発サーバが起動したらブラウザからhttp://localhost:5173/にアクセスすると初期画面が表示されます。
Tailwind CSSのインストールと設定
Reactの動作確認ができたのでTailwind CSSのインストールを行います。インストールと初期設定についてはTailwind CSSの公式のドキュメントを参考に行います。Tailwind CSSを使ったことがない人は以下の文書が参考になります。
npmコマンドを利用してTailwind CSSをインストールします。
% npm install -D tailwindcss postcss autoprefixer
Tailwind CSSの設定ファイルであるtailwind.config.jsファイルを作成するために以下のコマンドを実行します。プロジェクトディレクトリの直下にtailwind.config.jsとpostcss.config.tsファイルが作成されます。
% npx tailwindcss init -p
tailwind.config.js already exists.
Created PostCSS config file: postcss.config.js
tailwind.config.jsファイルでTailwind CSSが有効になるファイルをcontentで設定します。
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
theme: {
extend: {},
},
plugins: [],
};
srcディレクトリのindex.cssにTailwind CSSのディレクティブを設定します。
@tailwind base;
@tailwind components;
@tailwind utilities;
これでReactにおけるTailwind CSSの設定は完了です。設定完了後は、Tailwind CSSが有効になっているのか確認していましょう。
App.jsxファイルを開いて画面中央に文字が表示されるようにTailwind CSSのClassを設定します。
function App() {
return (
<div className="min-h-screen flex justify-center items-center">
Weather Application
</div>
);
}
export default App;
ブラウザの画面中央に文字が表示されたらTailwind CSSの設定が適切に行われ正常に動作していることになります。
Weatherカードの作成
APIを利用して外部のサービスであるOpenWeatherから天気の情報を取得する前に取得した情報を表示するためのカードコンポーネントを作成します。カードはコンポーネント化するためにApp.jsxとは別ファイルとして作成します。srcディレクトリにWeather.jsxファイルを作成します。幅はw-96(24rem)、高さはh-56(14rem)、背景色はブルーの四角を設定しています。componentsフォルダを作成してその下に作成しても問題ありません。importする際にパスに注意してください。
function Weather() {
return (
<div className="p-4">
<div className="w-96 h-56 m-auto bg-blue-500">
</div>
</div>
)
}
export default Weather
App.jsファイルでは作成したWeather.jsファイルをimportします。
import Weather from "./Weather"
function App() {
return (
<div className="min-h-screen flex justify-center items-center">
<Weather />
</div>
);
}
export default App;
ブラウザの中央には背景がブルーの四角が表示されます。
角に丸み(rounded-xl)をつけて、シャドウ(shadow-2xl)をつけます。
<div className="w-96 h-56 m-auto bg-blue-500 rounded-xl shadow-2xl">
マウスオーバーするとサイズが大きくなるようにtransfromの設定を行います。四角がゆっくりと浮き上がってくるように見えるようにtransitionの設定も行います。設定後はマウスオーバーしてサイズがふわっと大きくなることを確認してください。さらにduration-300, duration-500などを追加してフワッと大きくなる時間も確認して見てください。
<div className="w-96 h-56 m-auto bg-blue-500 rounded-xl
shadow-2xl
transform hover:scale-110 transition-transform ">
背景にグラデーションの設定を行います。
<div className="bg-gradient-to-r from-blue-500 to-blue-300
w-96 h-56 m-auto rounded-xl
shadow-2xl transform hover:scale-110
transition-transform ">
bg-gradient-to-r、from-blue-500, to-blue-300を設定すると左から色の濃いblue-500から色の薄いblue-300へと変わるグラデーションが設定できます。
グラデーションの入った四角が作成できたら天気の情報を表示させる四角の内部の設定を行っていきます。都市の名前と天気の画像を表示される場所を作成します。
<div className="bg-gradient-to-r from-blue-500 to-blue-300
w-96 h-56 m-auto rounded-xl shadow-2xl
transform hover:scale-110 transition-transform
text-white relative">
<div className="w-full px-8 absolute top-6">
<div className="flex justify-between">
<div>
<p className="font-light">City Name</p>
<p className="text-lg font-medium tracking-widest">
Tokyo
</p>
</div>
<div>
画像
</div>
</div>
</div>
</div>
flexboxのjustify-betweenで文字と画像の場所を両端になるように設定しています。tracking-widestは文字間隔、font-medium, font-lightで文字の太さの調整、text-lgで文字の大きさを設定しています。文字の色もtext-whiteで白に設定しています。位置を調整するためpaddingとrelative, absoluteで設定を行っています。
ブラウザで確認すると左側には都市名、右側には画像の文字が白で表示されます。画像の部分は後ほど文字ではなく画像が表示できるように設定を行います。
カード内に表示する天候、気温、湿度、日時のもTailwind CSSを使って装飾を行います。カード全体では下記のようなHTML文となります。
function Weather() {
return (
<div className="p-4">
<div
className="bg-gradient-to-r from-blue-500 to-blue-300
w-96 h-56 m-auto rounded-xl shadow-2xl
transform hover:scale-110 transition-transform
text-white relative"
>
<div className="w-full px-8 absolute top-6">
<div className="flex justify-between">
<div>
<p className="font-light">City Name</p>
<p className="text-lg font-medium tracking-widest">Tokyo</p>
</div>
<div>画像</div>
</div>
<div className="pt-2">
<p className="font-light">Weather Condition</p>
<p className="text-lg font-medium tracking-widest">Cloudy</p>
</div>
<div className="pt-6 pr-6">
<div className="flex justify-between">
<div>
<p className="font-light text-xs">Date</p>
<p className="font-bold tracking-more-wider text-sm">
2024-07-20
</p>
</div>
<div>
<p className="font-light text-xs">Temprature</p>
<p className="font-bold tracking-more-wider text-sm">20°C</p>
</div>
<div>
<p className="font-light text-xs">Humidity</p>
<p className="font-bold tracking-more-wider text-sm">40%</p>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Weather;
ブラウザで確認すると下記のカードデザインが作成できます。文字はすべて直接記述していますがこの後OpenWeatherサービスから取得したデータに置き換えていきます。
OpenWeatherのAPI
アカウントの作成
天気を表示するためのカードコンポーネントを作成したので次は天気の情報をAPIを利用して取得します。今回は無料で利用できるOpenWetherサービスを利用します。OpenWeatherを利用するためにはアカウントの作成が必要となるためアカウントの作成を行います。APIのリクエストに制限がありますが無料で利用することができます。
OpenWeatherのサイトにアクセスします。ページの右上にある”Sing in”ボタンをクリックしてください。
サインインの画面が表示されますがアカウントの作成ができていないので”Create an Account”をクリックしてください。
アカウント作成ページが表示されるので必要な項目を入力して”Create Account”ボタンをクリックしてください。
アカウント作成後に会社名と目的の入力フォームが表示されますが”Cancel”を選択することも可能です。
登録したメールアドレス宛にメールアドレスの確認メールが来ているのでメールを開き、”Verify your email”をクリックしてください。
APIのKeyの確認
アカウントが作成できるとデフォルトのAPI Keyを利用することができます。デフォルトのAPI Keyは表示されていますが利用できるまでに少し時間がかかる場合があります。利用ができない時にAPI keysでアクセスすると401 Invalid API keyエラーが表示されます。
実際にブラウザ上に表示されるエラーは”{“cod”:401, “message”: “Invalid API key. Please see https://openweathermap.org/faq#error401 for more info.”}”です。
API keysのタグをクリックすると名前がDefaultのKeyを利用することができます。Keyには任意の名前をつけて追加することも可能です。API Keyを追加は”Generate”ボタンから簡単に追加できますが利用できるまでに10分程度時間がかかる場合があります。
このDefaultのKeyを利用してOpenWeatherから天気の情報を取得します。
APIの利用方法の確認
APIを利用して外部のサービスから情報を取得するためには主に3つの情報が必要となります。1つは先ほど取得したAPIキー、2つ目はアクセスを行うURLで3つ目はパラメータです。それらの情報はドキュメントに記載されているのでドキュメントを確認する必要があります。
OpenWeaterの上部にあるAPIタグをクリックすると利用できるWether APIが表示されます。今回はCurrent Weather Data(現在の天候データ)を利用するので”API Doc”をクリックしてください。
ページを開くとすぐにAPIのURLとパラメータを確認することができます。q=に都市名とappidにKeyを設定することで現在のその都市の天気の情報が取得できることがわかります。ページをスクロールすると都市名だけではなく郵便番号や緯度、経度などでも取得できることがわかります。アクセス後に戻される情報のJSONの構造も記述されているのでしっかりと確認しておきしょう。構造については後ほど確認します。
ブラウザからの天気の取得
東京の現在の天気を取得してみましょう。URLとパラメータ、APIキーがわかっているのでブラウザからも取得することができます。ドキュメントに記載されたルールにしたがってURLを作成し、ブラウザのURL欄に記述します。
http://api.openweathermap.org/data/2.5/weather?q=TOKYO&appid=00b0681054136c5fXXXXXXXXXX
下記のようにブラウザ上にはOpenWeatherから戻されたJSONデータが表示されます。
Reactの設定
APIの設定
OpenWeatherのURLやAPIKeyについては.envファイルの中に保存し、Weatherコンポーネントからは.envに設定した環境変数からそれらの値を取得して利用します。
プロジェクトフォルダの下に.envファイルを作成してください。それぞれの変数には任意の名前をつけることができますが先頭にはVITEをつけてください。VITE_APP_OW_API_URLの値はOpenWeatherの全API共通のURLで現在の天気以外の天気予報などのAPIでも利用することができます。VITE_APP_OW_API_KEYについては各自の環境によって異なる値を設定します。VITE_APP_OW_ICON_URLはOpenWeateherが提供する天気のアイコン画像のURLです。アイコンについてはOpenWeatherから戻されるJSONデータの中に画像に関する情報が含まれています。後ほど説明を行います。
VITE_APP_OW_API_URL=https://api.openweathermap.org/data/2.5
VITE_APP_OW_API_KEY=00b0681054136cXXXXXXXXXXXXXXXXXX
VITE_APP_OW_ICON_URL=https://openweathermap.org/img/wn
Reactからの天気情報の取得
ブラウザから天気の情報が取得できることは確認できているのでReact上からAPIを使ってOpenWeatherにアクセスを行い天気情報を取得します。
React HooksのuseEffectを利用するのでWeather.jsxファイルにuseEffectをimportします。userEffectを利用することでWeatherコンポーネントのマウント時にfetch関数で天気の情報を取得しています。
import { useEffect } from 'react';
function Weather() {
useEffect(()=>{
fetch(
`${import.meta.env.VITE_APP_OW_API_URL}/weather/?q=Tokyo&APPID=${
import.meta.env.VITE_APP_OW_API_KEY
}`
)
.then(res => res.json())
.then(result => {
console.log(result);
});
},[]);
//略
ブラウザのコンソールに取得した情報が表示されます。取得したデータのnameからTokyoが確認できますが、tempが294.65となっているので摂氏で取得できるようにfetch関数でアクセスするURLに&units=metricを追加します。
取得した情報をブラウザ上にで利用するためには変数に保存する必要があります。変数に保存するためにReact HooksのuseStateを利用します。useEffectと同様にimportが必要となります。
取得したデータを保存する変数をdataとして初期値には空の配列を設定しています。
import { useState, useEffect } from 'react';
function Weather() {
const [data, setData] = useState([]);
useEffect(()=>{
fetch(`${import.meta.env.VITE_APP_OW_API_URL}/weather/?q=Tokyo&APPID=${import.meta.env.VITE_APP_OW_API_KEY}&units=metric`)
.then(res => res.json())
.then(result => {
setData(result);
},[])
//略
保存したdataをブラウザ上に表示させるためにはOpenWeatherから取得したJSONデータの構造を理解しておく必要があります。
ブラウザ上に表示させたい気温(temp)はmainの中にオブジェクトとして保存されており、天気の情報はweatherの中に配列として保存されています。weatherの配列にあるiconプロパティの02nが画像の情報を表しています。
JSONのデータ構造がわかったのでブラウズ上に表示できるように保存したdataを展開してみましょう。
dataのnameに都市名が含まれているのでこれまでTokyoと直接記述していた部分をdata.nameに変更します。
<p className="text-lg font-medium tracking-widest">
{ data.name }
</p>
ブラウザ上にはTokyoと表示されます。
つぎに天候を表すWeather Conditionの値をdata.weather[0].mainに変更します。weatherの配列のmainに天候情報が入っているためです。
<p className="text-lg font-medium tracking-widest">
{ data.weather[0].main }
//Cloudy
</p>
しかしブラウザ上には先ほどのdata.nameとは異なりundefinedエラーが表示されます。undefinedが表示されるので配列へのアクセス方法の処理を疑いますが原因はそれではありません。原因はOpenWeatherからデータを取得し変数dataに値を保存する前にブラウザ上での描写処理が行われるためまだ存在していなdataのweatherプロパティの中身にアクセスしようとしているためです。
この問題を解決するにはOpenWeatherからのデータを取得後に描写処理を行わせる必要があります。新たに変数loadingを追加し、OpenWeatherからデータ取得後にloadingの値を変更しloadingの値の変化で取得が完了したかどうかを判定します。loadingの初期値をtrueとし、データの取得が完了したらfalseに設定します。loadingの値がtrueの間は<div></div>が表示され、loadingがfalseになったらWeatherのカード部分が表示されます。
import { useState, useEffect } from 'react';
function Weather() {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(()=>{
fetch(`${import.meta.env.VITE_APP_OW_API_URL}/weather/?q=Tokyo&APPID=${import.meta.env.VITE_APP_OW_API_KEY}&units=metric`)
.then(res => res.json())
.then(result => {
setData(result);
setLoading(false);
})
},[])
if (loading) {
return <div></div>;
}
return (
<div className="p-4">
//略
再度動作確認するとエラーは解消されWeather ConditionにCloudsと表示されました。先ほどまでは直接Cloudyと記述していたのでOpenWeatherから取得したデータが反映されていることがわかります。
loadingのtrueの場合は空の<div></div>を設定していましたがTailwind CSSを利用することで簡単にローディングを表示させることができます。実際に追加するとWeatherのカードの中身が表示される前に一瞬ローディングが表示されることが確認できます。
if (loading) {
return <div>
<svg className="animate-spin -ml-1 mr-3 h-5 w-5 text-blue-500" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg></div>;
}
短い時間なのでローディングが確認できない場合は正常にローディングが表示されているか確認するためにuseEffectの処理をコメントしてみてください。
日付についてはdtの中にUnixのTimpstampで保存されているのでdayjs.jsライブラリを利用してフォーマットを行います。dayjsライブラリをnpmコマンドでインストールを行います。
% npm install dayjs
dayjsライブラリを利用するためにWeather.jsファイルでdayjsのimportを行います。
import { useState, useEffect } from 'react';
import dayjs from 'dayjs';
function Weather() {
//略
2021-03-16のフォマットで表示させるためにformat(‘YYYY-MM-DD’)としています。その他のフォーマットについてはdayjsのドキュメントを参考にしてください。
dayjs(data.ts).format('YYYY-MM-DD')}
アイコン、気温、湿度が表示されるようにdataプロパティのweatherの配列の値に置き換えます。
<div className="p-4">
<div className="bg-gradient-to-r from-blue-500 to-blue-300
w-96 h-56 m-auto rounded-xl shadow-2xl
transform hover:scale-110 transition-transform
text-white relative">
<div className="w-full px-8 absolute top-6">
<div className="flex justify-between">
<div>
<p className="font-light">City Name</p>
<p className="text-lg font-medium tracking-widest">
{ data.name }
</p>
</div>
<div>
<img src={`${process.env.REACT_APP_OW_ICON_URL}/${data.weather[0].icon}.png`}
alt={data.weather[0].description}/>
</div>
</div>
<div className="pt-2">
<p className="font-light">Weather Condition</p>
<p className="text-lg font-medium tracking-widest">
{ data.weather[0].main }
</p>
</div>
<div className="pt-6 pr-6">
<div className="flex justify-between">
<div>
<p className="font-light text-xs">
Date
</p>
<p className="font-bold tracking-more-wider text-sm">
{ dayjs(data.ts).format('YYYY-MM-DD')}
</p>
</div>
<div>
<p className="font-light text-xs">
Temprature
</p>
<p className="font-bold tracking-more-wider text-sm">
{data.main.temp}°C
</p>
</div>
<div>
<p className="font-light text-xs">
Humidity
</p>
<p className="font-bold tracking-more-wider text-sm">
{data.main.humidity}%
</p>
</div>
</div>
</div>
</div>
</div>
</div>
ブラウザで確認すると天気の状態にあったアイコンも表示されます。
propsを利用して他の都市の天気取得
コンポーネント化しているので別の都市の天気も表示できるように直接入力していたTokyoの文字列をpropsから渡される変数に変更します。propsから渡す値は親コンポーネントであるApp.jsxで設定します。
Weatherコンポーネントではpropsを受け取れるようにfunction Weahterの引数に{city_name}を設定しています。fetch関数の中でTokyoを{city.name}に置き換えています。またuseEffectの第2引数の配列にcity_nameを追加しています。
//略
function Weather({ city_name }) {
const [data, setData] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch(
`${import.meta.env.VITE_APP_OW_API_URL}/weather/?q=${city_name}&APPID=${
import.meta.env.VITE_APP_OW_API_KEY
}&units=metric`
)
.then((res) => res.json())
.then((result) => {
setData(result);
setLoading(false);
});
}, [city_name]);
//略
App.jsからはWeatherコンポーネントタグにcity_name属性を追加してParisを設定します。この設定でpropsとしてcity_nameを渡すことができます。
<Weather city_name="Paris" />
city_nameに入った値を使ってOpenWeatherにアクセスを行いそのcity_nameに入った都市の天気を取得します。ブラウザ上には指定したParis(パリ)の天気が表示されます。天候はClearで17度なのがわかります。
複数のコンポーネントを利用
Weatherコンポーネントを作成したので2つの都市の天気を表示させることも簡単に行うことができます。App.jsでWeatherタグを1つ追加して2つとします。追加したWeatherタグのcity_name属性の値を別の都市名に変更するだけです。晴れの確率の高いLos Angelesを設定してみます。Weatherコンポーネントをwrapしているdiv要素には横幅に収まらない場合は自動で次の行になるようにflex-wrapを追加しています。
<div className="min-h-screen flex justify-center items-center flex-wrap">
<Weather city_name="Paris" />
<Weather city_name="Los Angeles" />
Los Angeles(ロサンゼルス)の天気は曇り空であることがわかります。
背景色を変更
背景色をブルーに設定していましたが都市名と同様にpropsを使って親コンポーネントから設定できるように変更します。propsで渡される変数にcolor_nameを追加します。
function Weather({city_name, color_name}) {
背景色はclassNameの中で設定を行っているのでcolor_nameの値を反映できるように以下のように更新します。
<div className={`bg-gradient-to-r from-${color_name}-500 to-${color_name}-300
w-96 h-56 m-auto rounded-xl shadow-2xl
transform hover:scale-110 transition-transform
text-white relative`}>
App.jsのWetherrタグにcolor_nameを追加し、色の設定を行います。設定できる色はカスタマイズで追加しない限りTailwind CSSのClassに登録されている色のみです。
<Weather city_name="Paris" color_name="red" />
<Weather city_name="Los Angeles" color_name="green" />
動的に色が設定されているためカラーが表示されない場合はtailwind.config.jsファイルに事前に利用するカラーを設定しておきます。
/** @type {import('tailwindcss').Config} */
export default {
content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'],
safelist: ['from-blue-500', 'to-blue-300','from-green-500', 'to-green-300', 'from-red-500', 'to-red-300', 'from-yellow-500', 'to-yellow-300'],
theme: {
extend: {},
},
plugins: [],
};
オブジェクトのcitiesを準備して4つの都市名とカラー名と設定し、map関数を利用して展開してWeatherコンポーネントを4つ作成します。
import Weather from "./Weather"
function App() {
const cities = [
{
city_name: 'Tokyo',
color_name: 'blue'
},
{
city_name: 'London',
color_name: 'yellow'
},
{
city_name: 'Paris',
color_name: 'green'
},
{
city_name: 'Los Angeles',
color_name: 'red'
},
];
return (
<div className="min-h-screen flex justify-center items-center flex-wrap">
{
cities.map((city,index) =>
<Weather
key={index}
city_name={city.city_name}
color_name={city.color_name}
/>)
}
</div>
);
}
export default App;
色はpropsで渡しましたがOpenWeatherから返される天気の情報から色を設定することも可能です。またApp.jsに入力フォームを追加し、入力した都市の天気を表示させることも可能です。Reactのprops, useEffect, useStateを使うだけでさまざまなことを行うことができます。ぜひチャレンジしてみてください。