Reactから外部APIを呼び出す
バックエンドと疎通を取る実装のサンプルです。APIから受け取ったメッセージを画面表示します。
cf.
ディレクトリ構成
.
└── src
└── features
└── me
├── meApi.ts # API呼び出し
├── meSlice.ts # 画面の状態管理
├── meThunks.ts # フロント側のビジネスロジック
└── Me.tsx # 画面のレンダリング
サンプルコード
features/me/meApi.ts
APIの呼び出しのみを行います。
/**
* ユーザ情報取得APIのリクエストモデルです。
*/
type MeRequest = {
name: string;
};
/**
* ユーザ情報取得APIからの正常レスポンスを格納するモデルです。
*/
type MeSuccess = {
ok: true;
name: string;
age: number;
};
/**
* ユーザ情報取得APIからの異常レスポンスを格納するモデルです。
*/
type MeError = {
ok: false;
message: string;
};
/**
* ユーザ情報取得APIのレスポンスモデルです。
*/
type MeResponse = MeSuccess | MeError;
/**
* ユーザ情報取得APIを呼び出します。
*
* @param req ユーザ情報検索リクエスト
* @returns ユーザ情報
*/
export const me = async (req: MeRequest): Promise<MeResponse> => {
const url = new URL("/api/v1/me", window.location.origin);
url.search = new URLSearchParams({
name: req.name,
}).toString();
const res = await fetch(url.toString(), {
method: "GET",
});
const data = await res.json();
if (!res.ok) {
return { ok: false, message: data.message };
}
return { ok: true, name: data.name, age: data.age };
};
features/me/meThunks.ts
フロント側で行う業務処理を実装します。API呼び出し関数を実行し、その結果に対応した型を戻すことでslice側で状態の更新が行われます。
import { createAsyncThunk } from "@reduxjs/toolkit";
import { me } from "./meApi";
/**
* リクエスト情報を画面から渡すモデルです。
*/
type FetchMeForm = {
name: string;
};
/**
* ユーザ情報取得成功時の状態をactionに渡すモデルです。
*/
type FetchMeSuccess = {
name: string;
age: number;
};
/**
* ユーザ情報取得失敗時の状態をactionに渡すモデルです。
*/
type FetchMeError = {
message: string;
};
/**
* ユーザ情報取得APIを呼び出して取得したユーザ情報をstateに保持します。
*/
export const fetchMeThunk = createAsyncThunk<
FetchMeSuccess,
FetchMeForm,
{ rejectValue: FetchMeError }
>("me/fetch", async (form, { rejectWithValue }) => {
try {
const res = await me({ name: form.name });
if (!res.ok) {
return rejectWithValue({ message: res.message });
}
return { name: res.name, age: res.age };
} catch {
return rejectWithValue({ message: "不明なエラーが発生しました。" });
}
});
features/me/meSlice.ts
API呼び出し時の状態管理をextraReducersで行います。
import { createSlice } from "@reduxjs/toolkit";
import { fetchMeThunk } from "./meThunks";
/**
* ユーザ情報表示コンポーネントの状態を保持するstateです。
*/
type MeState = {
profile: string;
loading: boolean;
errorMessage?: string;
};
const initialState: MeState = {
profile: "",
loading: false,
errorMessage: undefined,
};
const meSlice = createSlice({
name: "me",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
/**
* ユーザ取得API呼び出し開始時の状態遷移
*/
.addCase(fetchMeThunk.pending, (state) => {
state.loading = true;
})
/**
* ユーザ取得API呼び出し正常終了の状態遷移
*/
.addCase(fetchMeThunk.fulfilled, (state, action) => {
state.profile = action.payload.name + " (" + action.payload.age + ")";
state.errorMessage = "";
state.loading = false;
})
/**
* ユーザ取得API呼び出し異常終了時の状態遷移
*/
.addCase(fetchMeThunk.rejected, (state, action) => {
state.errorMessage = action.payload?.message;
state.loading = false;
});
},
});
export default meSlice.reducer;
features/me/Me.tsx
stateの値を使って画面のレンダリングを行います。
import { useAppDispatch, useAppSelector } from "../../app/hooks";
import { fetchMeThunk } from "./meThunks";
/**
* ユーザ情報を取得・表示するコンポーネントです。
*
* @returns ユーザ情報表示コンポーネント
*/
export const Me = () => {
const meState = useAppSelector((state) => state.me);
const dispatch = useAppDispatch();
return (
<div>
{meState.errorMessage ? (
<div>{meState.errorMessage}</div>
) : (
<div>{meState.profile}</div>
)}
<button onClick={() => dispatch(fetchMeThunk({ name: "nob" }))}>
検索
</button>
</div>
);
};