【NextJS】Redux-createAsyncThunk

1. 概要

前回はReduxを使いコンポネント間で値を共有する内容についてでした。今回はRedux Thunksを使いAPI呼び出しの非同期処理についてです。

対象としては開発を1年程やってて自分で最初から開発してみたい方になります。そのため細かい用語などの説明はしません。

2. nodeのインストール

こちらを参考

3. プロジェクトを作成

こちらを参考

4. 必要なライブラリをインストール

こちらを参考

5. ソースコード

※前回より差分のみを記載

5-1-1. src/app/redux/redux02/page.module.scss

.component {
  color: blue;
  & ul {
    margin-left: 20px;
    & li {
      list-style: disc;
    }
  }
}

5-1-2. src/app/redux/redux02/store.ts

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import textSlice from "./text-slice";
import imageSlice from "./image-slice";

const reducers = combineReducers({
  text: textSlice,
  image: imageSlice,
});

const store = configureStore({
  reducer: reducers,
});

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export default store;

5-1-3. src/app/redux/redux02/hooks.ts

import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
import type { RootState, AppDispatch } from "./store";

export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

5-1-4. src/app/redux/redux02/text-slice.tsx

import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import type { RootState } from "./store";

export type TextAction = {
  text: string;
};

type TextState = {
  text: string;
};

const initialState: TextState = {
  text: "",
};

export const textSlice = createSlice({
  name: "text",
  initialState,
  reducers: {
    setText: (state, action: PayloadAction<TextAction>) => {
      const { text } = action.payload;
      state.text = text;
    },
  },
});

const changeText = (state: RootState): TextState => state.text;

export { changeText };
export const { setText } = textSlice.actions;
export default textSlice.reducer;

5-1-5. src/app/redux/redux02/text-box.tsx

import { ChangeEvent } from "react";
import { Divider } from "@mui/material";
import Box from "@mui/material/Box";
import TextField from "@mui/material/TextField";
import { useAppSelector, useAppDispatch } from "./hooks";
import { AppDispatch } from "./store";
import { setText, changeText } from "./text-slice";

const TextBox = () => {
  const textObj = useAppSelector(changeText);
  const dispatch: AppDispatch = useAppDispatch();
  const handleChange = (
    e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => {
    dispatch(setText({ text: e.target.value }));
  };
  return (
    <Box
      sx={{
        width: "100%",
        p: 2,
        border: "1px dashed grey",
        borderRadius: "20px",
        "&:hover": {
          backgroundColor: "pink",
          opacity: [0.9, 0.8, 0.7],
        },
      }}
    >
      <TextField
        fullWidth
        label="Hello"
        color="secondary"
        onChange={handleChange}
      />
      <Divider />
      {textObj.text}
    </Box>
  );
};

export default TextBox;

5-1-6. src/app/redux/redux02/image-slice.ts

import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
import { RootState } from "./store";

type ImageState = {
  src: string;
  status: string;
};

const initialState: ImageState = {
  src: "",
  status: "",
};

export const imageSlice = createSlice({
  name: "image",
  initialState,
  reducers: {},
  extraReducers(builder) {
    builder
      .addCase(fetchImage.pending, (state) => {
        state.status = "loading";
      })
      .addCase(fetchImage.fulfilled, (state, action) => {
        state.status = "done";
        const { message } = action.payload;
        state.src = message;
      })
      .addCase(fetchImage.rejected, (state) => {
        state.status = "error";
      });
  },
});

const fetchImage = createAsyncThunk("image/fetchImage", async () => {
  const response = await fetch("https://dog.ceo/api/breeds/image/random");
  return response.json();
});

const pickImage = (state: RootState) => state.image;

export { fetchImage, pickImage };
export default imageSlice.reducer;

5-1-7. src/app/redux/redux02/image-box.tsx

import { Button, Divider } from "@mui/material";
import Box from "@mui/material/Box";
import { useAppSelector, useAppDispatch } from "./hooks";
import { fetchImage, pickImage } from "./image-slice";

const ImageBox = () => {
  const dispatch = useAppDispatch();
  const image = useAppSelector(pickImage);
  const handleClick = () => {
    dispatch(fetchImage());
  };
  return (
    <Box
      sx={{
        width: "100%",
        p: 2,
        border: "1px dashed grey",
        borderRadius: "20px",
        "&:hover": {
          backgroundColor: "lightskyblue",
          opacity: [0.9, 0.8, 0.7],
        },
      }}
    >
      <Button
        variant="contained"
        size="medium"
        color="secondary"
        onClick={() => handleClick()}
      >
        Dog Image Fetch (Random)
      </Button>
      <Divider sx={{ paddingBottom: 2 }} />
      {image.status === "done" ? <img src={image.src} /> : image.status}
    </Box>
  );
};

export default ImageBox;

5-1-8. src/app/redux/redux02/child.tsx

import { Stack, Divider } from "@mui/material";
import scss from "./page.module.scss";
import GoBack from "@/lib/components/go-back";
import TextBox from "./text-box";
import ImageBox from "./image-box";

const Child = () => {
  return (
    <div className={scss.component}>
      <GoBack />
      <br />
      <br />
      <ul>
        <li>Updating the screen</li>
        <ul>
          <li>createAsyncThunk</li>
        </ul>
      </ul>
      <br />
      <Stack spacing={1} sx={{ width: "50%" }}>
        <TextBox />
        <Divider />
        <ImageBox />
      </Stack>
    </div>
  );
};

export default Child;

5-1-9. src/app/redux/redux02/page.tsx

"use client";

import { Provider } from "react-redux";
import store from "./store";
import Child from "./child";

const Redux02 = () => {
  return (
    <Provider store={store}>
      <Child />
    </Provider>
  );
};

export default Redux02;

5-1-10. src/app/redux/page.module.scss

.components {
  color: blue;
  & ul {
    margin-left: 20px;
    & li {
      list-style: disc;
    }
  }
}

5-1-11. src/app/redux/page.tsx

"use client";

import React from "react";
import { Link } from "@mui/material";

import scss from "./page.module.scss";

const Redux = () => {
  return (
    <div className={scss.components}>
      <ul>
        <li>
          <Link href="/redux/redux01" underline="hover">
            Redux01
          </Link>
        </li>
        <li>
          <Link href="/redux/redux02" underline="hover">
            Redux02
          </Link>
        </li>
      </ul>
    </div>
  );
};

export default Redux;

6. サーバーを起動

npm run dev

7. ブラウザで確認

  • http://localhost:3000

8. ディレクトリの構造

.
├── README.md
├── next-env.d.ts
├── next.config.js
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
│   ├── next.svg
│   └── vercel.svg
├── src
│   ├── app
│   │   ├── components
│   │   │   ├── component01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component02
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component03
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── component04
│   │   │   │   ├── checkbox-demo.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   ├── radio-demo.tsx
│   │   │   │   └── select-demo.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── events
│   │   │   ├── event01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── favicon.ico
│   │   ├── globals.css
│   │   ├── globals.scss
│   │   ├── hooks
│   │   │   ├── hook01
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook02
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook03
│   │   │   │   ├── child.tsx
│   │   │   │   ├── counter-provider.tsx
│   │   │   │   ├── grandchild.tsx
│   │   │   │   ├── myself.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook04
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook05
│   │   │   │   ├── child.tsx
│   │   │   │   ├── counter-provider.tsx
│   │   │   │   ├── grandchild.tsx
│   │   │   │   ├── myself.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   └── page.tsx
│   │   │   ├── hook06
│   │   │   │   ├── child.tsx
│   │   │   │   ├── page.module.scss
│   │   │   │   ├── page.tsx
│   │   │   │   ├── play-provider.tsx
│   │   │   │   ├── text-box.tsx
│   │   │   │   └── video-player.tsx
│   │   │   ├── page.module.scss
│   │   │   └── page.tsx
│   │   ├── layout.module.scss
│   │   ├── layout.tsx
│   │   ├── page.module.scss
│   │   ├── page.tsx
│   │   └── redux
│   │       ├── page.module.scss
│   │       ├── page.tsx
│   │       ├── redux01
│   │       │   ├── child.tsx
│   │       │   ├── counter-slice.ts
│   │       │   ├── grandchild.tsx
│   │       │   ├── hooks.ts
│   │       │   ├── myself.tsx
│   │       │   ├── page.module.scss
│   │       │   ├── page.tsx
│   │       │   └── store.ts
│   │       └── redux02
│   │           ├── child.tsx
│   │           ├── hooks.ts
│   │           ├── image-box.tsx
│   │           ├── image-slice.ts
│   │           ├── page.module.scss
│   │           ├── page.tsx
│   │           ├── store.ts
│   │           ├── text-box.tsx
│   │           └── text-slice.ts
│   ├── lib
│   │   ├── common
│   │   │   ├── definitions.ts
│   │   │   └── sidebar-links.tsx
│   │   ├── components
│   │   │   ├── alert-snackbar.tsx
│   │   │   ├── go-back.tsx
│   │   │   └── spacer.tsx
│   │   ├── footer.tsx
│   │   ├── header.tsx
│   │   ├── sidebar.tsx
│   │   └── utils
│   │       └── util.ts
│   └── scss
│       └── common
│           ├── _index.scss
│           ├── _mixin.scss
│           ├── _mq.scss
│           └── _variables.scss
├── tailwind.config.ts
└── tsconfig.json

26 directories, 92 files

9. 備考

今回はRedux Thunksを使いAPI呼び出しの非同期処理についてでした。

10. 参考

投稿者プロフィール

Sondon
開発好きなシステムエンジニアです。
卓球にハマってます。

関連記事

  1. 【NextJS】Cognito with Amplify(Gen2)+…

  2. 【NextJS】Internationalization(i18n) …

  3. 【NextJS】Checkbox・Radio Group

  4. 【NextJS】ChatApp with Realtime updat…

  5. 【NextJS】Events

  6. 【NextJS】AWS SAMを使いCLIでデプロイしたLambda関…

最近の記事

  1. raspberrypi

制作実績一覧

  1. Checkeys