シングルページアプリケーションの開発に際し、データの取得、保持、加工をストアに任せる設計を経験した。実際に経験したのはNuxt3のPiniaだが、ReactのReduxでも同じことができるだろうということで忘備録として記録する。
参考にしたRedux公式ドキュメント: 『Redux Essentials, Part 7: RTK Query Basics』
https://redux.js.org/tutorials/essentials/part-7-rtk-query-basics
前提条件
Reactアプリケーションにおいて、Reduxが使用ができるようになっていること。
以下の記事をもとに解説する。
【Next.js14】「Redux」を使って状態管理を実現する手順
環境
"typescript": "^5"
"next": "14.0.3",
"react": "^17.0.0 || ^18.0.0",
"react-dom": "^17.0.0 || ^18.0.0",
"react-hook-form": "^7.48.2",
"react-redux": "^9.1.0",
"@reduxjs/toolkit": "^2.2.3",
実装要件
(1)ページ: http://localhost:3000/test/redux/users
を用意する
(2)https://jsonplaceholder.typicode.com/users
からユーザー一覧を取得し、stateに同期する処理を作成する
(3)コンポーネントからデータ取得を実行する
(4)Reduxのデータを用いて、ユーザー一覧を描画する
ファイル群
// ユーザー情報の取得と保持を管理するストア
src/lib/redux/features/usersSlice.ts
// configureStoreを行なっているファイル
src/lib/redux/store.ts
// http://localhost:3000/test/redux/usersのコンポーネントファイル
src/pages/test/redux/users/index.tsx
src/lib/redux/features/usersSlice.ts
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
import { RootState } from "../store";
// ユーザー情報の型
interface User {
id: number;
name: string;
username: string;
email: string;
address: {
street: string;
suite: string;
city: string;
zipcode: string;
geo: {
lat: string;
lng: string;
};
};
phone: string;
website: string;
company: {
name: string;
catchPhrase: string;
bs: string;
};
}
// ユーザー情報の取得処理
export const fetchUsers = createAsyncThunk("/users/fetchUsers", async () => {
const response = await fetch("https://jsonplaceholder.typicode.com/users");
return response.json();
});
export type UserSliceState = {
data: User[];
status: "idle" | "pending" | "succeeded" | "failed";
error: undefined | string;
};
const initialState: UserSliceState = {
data: [],
status: "idle",
error: undefined,
};
const userSlice = createSlice({
name: "users",
initialState,
reducers: {
initUsers: (state) => {
state.data = [];
},
},
// extraReducersにハンドラーを追加し、pending/fulfilled/rejectedのケースを処理
extraReducers: (builder) => {
builder.addCase(fetchUsers.pending, (state) => {
state.status = "pending";
});
builder.addCase(fetchUsers.fulfilled, (state, action) => {
state.data = action.payload;
state.status = "succeeded";
});
builder.addCase(fetchUsers.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const selectUsers = (state: RootState) => state.users;
export const { initUsers } = userSlice.actions;
export default userSlice.reducer;
ポイントになるのはcreateAsyncThunk
。非同期のデータフェッチにはcreateAsyncThunk
を使用する。createAsyncThunk
は、非同期処理の実行状況に応じてpending
、fulfilled
、rejected
の3つのactionにアクセスできる。そのため、下記で記載しているように、fetchUsers.pending
、fetchUsers.fulfilled
、fetchUsers.rejected
に応じて処理を分岐することができる。
src/lib/redux/store.ts
import { configureStore } from "@reduxjs/toolkit";
import { useDispatch, useSelector, useStore } from "react-redux";
import counterSlice from "./features/counterSlice";
// 追加
import usersReducer from "@/lib/redux/features/usersSlice";
export const reduxStore = () => {
return configureStore({
reducer: {
counterStore: counterSlice,
// 追加
users: usersReducer,
},
});
};
export type AppStore = ReturnType<typeof reduxStore>;
export type RootState = ReturnType<AppStore["getState"]>;
type AppDispatch = AppStore["dispatch"];
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();
src/pages/test/redux/users/index.tsx
import {
fetchUsers,
initUsers,
selectUsers,
} from "@/lib/redux/features/usersSlice";
import { useAppDispatch, useAppSelector } from "@/lib/redux/store";
import {
Alert,
Box,
Button,
Container,
List,
ListItem,
ListItemText,
} from "@mui/material";
import { useCallback } from "react";
export default function PhotoPage() {
const dispatch = useAppDispatch();
const users = useAppSelector(selectUsers);
const handleClickGetUser = useCallback(() => {
dispatch(fetchUsers()).catch((error) => error.message);
}, [dispatch]);
const handleClickInit = () => {
dispatch(initUsers());
};
return (
<Container maxWidth="md">
<Button onClick={handleClickGetUser}>GET DATA</Button>
<Button onClick={handleClickInit}>初期化</Button>
<Box>
{users.status === "failed" && (
<Alert severity="error">This is an error Alert.</Alert>
)}
</Box>
<Box>
{users.status === "succeeded" && (
<List>
{users.data.map((item) => (
<ListItem key={item.id}>
<ListItemText primary={item.name}></ListItemText>
</ListItem>
))}
</List>
)}
</Box>
</Container>
);
}
createAsyncThunk
で処理の状態をセットできるため、pending、fulfilled、rejectedの状態に応じて、表示を出し分けることができるようになる。
動作
以下の「GET DATA」を押下すると、
ユーザー情報の取得処理が走り、ユーザー一覧が表示される。
通信に失敗した場合は、アラートが表示される。
以上。
コメント