React Reduxを使ってNHKの番組表のAPIを読み込みんでみます。
今回は、NHK番組表APIの「Program List API (Ver.2)」を利用します。
Create React AppでReactとReduxをインストール
まず、Create React AppでReactをインストールします。
–template redux をつけると、必要なReduxも一緒にインストールしてくれます。
npx create-react-app nhk-api –template redux
名前は nhk-api としました。
インストール完了後、
cd my-app
npm start
と打つと、無事、http://localhost:3000 にアプリが表示されます。
デフォルトでカウンターアプリが表示されます。

ディレクトリ構造はこんな感じです。

不要なファイルやコードを削除
デフォルトから不要なファイルやコードを削除していきます。
まず、既存のCSS(index.css, App.css)は使わないので中身だけすべて削除しておきます。
featuresディレクトリのcounter以下はまるごと削除します。
次にApp.jsの不要なコードを削除します。
上の方のCounterコンポーネントのimportと、retrurn ()の中身です。
現在のApp.jsはこうなっています。
import React from "react";
import logo from "./logo.svg";
import "./App.css";
function App() {
return <></>;
}
export default App;app/store.js内もいくつか削除しておきます。
counterReducer の import と counter: counterReducer を削除します。
現在のstore.jsはこちら。
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {},
});
この状態で http://localhost:3000 にアクセスしても真っ白です。
NHK番組表APIのAPIキーを取得
NHK番組表APIのAPIキーを取得するには、ユーザ登録が必要です。
https://api-portal.nhk.or.jp/ にアクセスしてユーザ登録を行います。

名前と苗字、メールアドレスとパスワードを入力して、termsを読んでチェックを入れて、Create Accountのボタンを押すとページが切り替わります。
上で入力したアドレスに確認メールが飛んでいるので、メール内のURLからNHK番組表APIサイトにアクセスして、ログインします。
ログインできたら、「登録済みアプリはこちら」を押します。

まだ、アプリはないので「+NEW APP」を押して新しいアプリを作ります。

Overviewに、アプリの名前(App Name)を入力します。半角英数記号しか使えないようです。
Descriptionはオプションです。

右下のActionsをEnableにしておいて、Saveを押します。

画面遷移したあと、API Keyが取得できます。後で使います。

List の featureを作成する
現在空っぽのfeaturesフォルダ以下に、listフォルダを作成します。
さらにその下に、List.jsとlistSlice.jsというファイルを作成します。
それぞれ書いていきましょう。
listSlice.js
listSlice.jsから。
Redux ToolKitから、createAsyncThunkとcreateSliceをインポートします。
また、Program List API (Ver.2)のページを参考に、area, service, dateを用意しておきます。
areaの130は東京で、serviceのg1はNHK総合1ですね。
dateは、当日の日付をYYYY-MM-DD形式に変換するように書いています。
apikeyは、先程取得したものを入れます。ここでは黒丸にしています。
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const area = 130;
const service = "g1";
const date = new Date().toISOString().split("T")[0];
const apikey = "●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●";その下にCreateAsyncThunkのコードを書きます。
fetch内のURLは、Program List API (Ver.2)のページを参考にしています。
ResponseをJSON形式で返します。
あとでList.js内で使うので、exportしています。
export const fetchList = createAsyncThunk("list/fetchList", async () => {
const response = await fetch(
`https://api.nhk.or.jp/v2/pg/list/${area}/${service}/${date}.json?key=${apikey}`
);
return await response.json();
});その下に、createSliceのコードを書きます。
nameはlistにしました。
stateのlistの中に空のオブジェクトを用意して、fetchListがうまく行ったら(fulfilled)番組表のオブジェクトが入ります。
読み込み中表示のためにisLoadingをfalseにしておいて、読み込み中だけ(pending)trueになるようにしています。
アクセスが拒否されたとき(rejected)は、isRejectedはtrueになります。
const listSlice = createSlice({
name: "list",
initialState: { list: {}, isLoading: false, isRejected: false },
extraReducers: (builder) => {
builder
.addCase(fetchList.pending, (state) => {
state.isLoading = true;
state.isRejected = false;
})
.addCase(fetchList.fulfilled, (state, action) => {
state.isLoading = false;
state.isRejected = false;
state.list = action.payload;
})
.addCase(fetchList.rejected, (state) => {
state.isLoading = false;
state.isRejected = true;
});
},
});その下に、exportのコードを4行書きます。
1行目は、store.jsでimportするreducerです。
2行目から4行目はそれぞれ、番組表リストオブジェクト、isLoading、isRejectedです。こちらは、List.jsで使います。
export default listSlice.reducer;
export const selectList = (state) => state.list.list;
export const selectIsLoading = (state) => state.list.isLoading;
export const selectIsRejected = (state) => state.list.isRejected;いったんここで、listSlice.jsのコード全体を載せておきます。
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
const area = 130;
const service = "g1";
const date = new Date().toISOString().split("T")[0];
const apikey = "●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●●";
export const fetchList = createAsyncThunk("list/fetchList", async () => {
const response = await fetch(
`https://api.nhk.or.jp/v2/pg/list/${area}/${service}/${date}.json?key=${apikey}`
);
return await response.json();
});
const listSlice = createSlice({
name: "list",
initialState: { list: {}, isLoading: false, isRejected: false },
extraReducers: (builder) => {
builder
.addCase(fetchList.pending, (state) => {
state.isLoading = true;
state.isRejected = false;
})
.addCase(fetchList.fulfilled, (state, action) => {
state.isLoading = false;
state.isRejected = false;
state.list = action.payload;
})
.addCase(fetchList.rejected, (state) => {
state.isLoading = false;
state.isRejected = true;
});
},
});
export default listSlice.reducer;
export const selectList = (state) => state.list.list;
export const selectIsLoading = (state) => state.list.isLoading;
export const selectIsRejected = (state) => state.list.isRejected;store.js
app/store.jsは不要なコードを削除してあるので、少し手を加えるだけです。
listSlice.jsからexportしたreducerをimportして、configureStoreのreducerオブジェクトに加えるだけです。
reducerオブジェクト内のlistは、listSlice.jsのcreateSliceのnameと同一にします。
store.js全体のコードを載せておきます。
import { configureStore } from "@reduxjs/toolkit";
import listReducer from "../features/list/listSlice";
export const store = configureStore({
reducer: { list: listReducer },
});store.jsはこんな感じです。
List.js
List.jsを見ていきます。
importするのは、reactからuseEffect、react-reduxから useDispatch, useSelector。
先程作ったlistSlice.jsから、fetchList, selectList, selectIsLoading, selectIsRejected。
そして、今回は同じディレクトリにList.cssを用意しています。
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
fetchList,
selectList,
selectIsLoading,
selectIsRejected,
} from "./listSlice";
import "./List.css";その下に、Listコンポーネントを書いていきます。
後でApp.jsでimportするので、exportしておきます。
function List() {
return;
}
export default List;function List() {}の中身を書いていきます。
まず、dispatchファンクションを使えるようにします。
const dispatch = useDispatch();次にuseSelector()を使って、Redux storeのstateを呼び出せるようにします。
listSlice.jsで3つexportしていたぶんですね。
const list = useSelector(selectList);
const isLoading = useSelector(selectIsLoading);
const isRejected = useSelector(selectIsRejected);次は、ReactのuseEffect()を使って、fetchList()をdispatch()します。
fetchList()は、listSlice.jsで定義した、NHK番組表APIのデータを集めてくる機能です。
useEffect()を使わないと、延々とdispatch()が発動され続けてしまいます。
useEffect(() => {
dispatch(fetchList());
}, [dispatch]);いよいよブラウザに表示されるJSX部分に移ります。
ロード中の場合、isLoadingがtrueになり Loading… が表示されます。
また、NHK番組表APIに接続が拒否されたらisRejectedがtrueになり Rejected.が表示されるようにしています。
if (isLoading) {
return <p>Loading...</p>;
}
if (isRejected) {
return <p>Rejected.</p>;
}問題なくAPIが読み込まれたら、取得したデータオブジェクトからarrayをmap()で繰り返し表示します。
日時の表示フォーマットにはIntl.DateTimeFormat()を使っています。
return (
<ul>
{list.list?.g1.map((program) => {
return (
<li className="program" key={program.id}>
<div className="time">
{new Intl.DateTimeFormat("ja-JA", {
dateStyle: "short",
timeStyle: "short",
}).format(new Date(program.start_time))}
</div>
<h2 className="title">{program.title}</h2>
<p className="subtitle">{program.subtitle}</p>
</li>
);
})}
</ul>
);最後に、Listコンポーネントをexportします。
後ほどApp.jsでimportして使います。
List.jsの全体のコードを載せておきます。
import { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import {
fetchList,
selectList,
selectIsLoading,
selectIsRejected,
} from "./listSlice";
import "./List.css";
function List() {
const dispatch = useDispatch();
const list = useSelector(selectList);
const isLoading = useSelector(selectIsLoading);
const isRejected = useSelector(selectIsRejected);
useEffect(() => {
dispatch(fetchList());
}, [dispatch]);
if (isLoading) {
return <p>Loading...</p>;
}
if (isRejected) {
return <p>Rejected.</p>;
}
return (
<ul>
{list.list?.g1.map((program) => {
return (
<li className="program" key={program.id}>
<div className="time">
{new Intl.DateTimeFormat("ja-JA", {
dateStyle: "short",
timeStyle: "short",
}).format(new Date(program.start_time))}
</div>
<h2 className="title">{program.title}</h2>
<p className="subtitle">{program.subtitle}</p>
</li>
);
})}
</ul>
);
}
export default List;
List.css
List.jsを見やすくするために、少しCSSを書きました。
コードを載せておきます。
ul {
list-style-type: none;
padding: 0;
max-width: 800px;
}
.program {
border: 1px solid #cccccc;
margin: 10px auto;
padding: 20px;
}
.time {
color: #666666;
font-size: 14px;
}
.title {
font-size: 14px;
}
.subtitle {
color: #666666;
font-size: 12px;
}
App.js
srcディレクトリ直下のApp.jsでListコンポーネントをimportします。
return()の中に、<List />コンポーネントを配置しています。
全体のコードです。
import React from "react";
import "./App.css";
import List from "./features/list/List";
function App() {
return (
<>
<h1>NHK番組表リスト 東京 NHK総合1</h1>
<List />
</>
);
}
export default App;これでコードは完成です。
ディレクトリ構造はこうなっています。

実際に動かしてみる
では、動かしてみましょう
npm start でReactを動かして、http://localhost:3000 にアクセスします。

きちんと表示されていますね。
一瞬ですがリストが表示される前にLoading…と表示されています。
本当はこの程度であればReduxを使うまでもないんでしょうが、テストということで。
あと、本来ならAPIキーはバックエンド側に書くべきです。
コードを公開する場合は、.envを使うなりしてGithubなどでもAPIキーが閲覧できないようにしましょう。
以上です。
言語化するのは難しいですね…。


コメント