0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

React(TypeScript + ReduxToolkit)の構成でアプリを作成しました【簡易TODOアプリ】

Last updated at Posted at 2022-01-15

開発準備

$ npx create-react-app <プロジェクト名> --template redux-typescript
# or
$ yarn create react-app <プロジェクト名> --template redux-typescript

formatter等の設定

PrettierとESLintを基本からまとめてみた【ReactのアプリにESLintとPrettierを導入】

必要なパッケージのインストール

① node-sass

$ yarn add node-sass

② react-hook-form

$ yarn add react-hook-form

③ material-ui

// with npm
$ npm i @mui/material @emotion/react @emotion/styled

// with yarn
$ yarn add @mui/material @emotion/react @emotion/styled

UIを構築する

① 使わないファイルを削除する

App.test.tsx
logo.svg
setupTests.ts

② App.tsxの編集する

App.tsx
import React from "react";
import Header from "./components/header/Header";
import styles from "./App.module.scss";

const App: React.FC = () => {
  return (
    <div className={styles.root}>
      <div className={styles.wrapper}>
        <Header />
      </div>
    </div>
  );
};
export default App;

③ App.module.scssの作成する

④ src/components/header/Header.tsx と Header.module.scss を作成する

src/components/header/Header.tsx
import React from "react";
import { AppBar, Toolbar, Typography } from "@mui/material";
import styles from "./Header.module.scss";

const Header: React.FC = () => {
  return (
    <div className={styles.root}>
      <AppBar position="static">
        <Toolbar>
          <Typography variant="h6" className={styles.title}>
            News
          </Typography>
        </Toolbar>
      </AppBar>
    </div>
  );
};
export default Header;

⑤ App.tsx を編集する

App.tsx
import React from "react";
import Header from "./components/header/Header";
import styles from "./App.module.scss";

const App: React.FC = () => {
  return (
    <div>
      <Header />
    </div>
  );
};
export default App;

⑥ App.module.scss を編集する

App.module.scss
.root {
  background-color: antiquewhite;
  height: 100vh;
  width: 100vw;
  display: flex;
  justify-content: center;
  align-items: center;
}
.wrapper {
  height: 100vh;
  width: 100vw;
  border-radius: 10px;
  background-color: 3f1f2f7;
  padding: 10px 40px;
}

⑦ src/components/Header.tsx を編集する

src/components/Header.tsx
import React from "react";
import { AppBar, Toolbar, Typography } from "@mui/material";
import styles from "./Header.module.scss";

const Header: React.FC = () => {
  return (
    <div className={styles.root}>
      <AppBar position="static" className={styles.app_bar}>
        <Toolbar className={styles.tool_bar}>
          <Typography variant="h6" className={styles.title}>
            Redux Toolkit Todo
          </Typography>
        </Toolbar>
      </AppBar>
    </div>
  );
};

export default Header;

⑧ src/components/Header.module.scss を編集する

src/components/Header.module.scss

import React from "react";
import { AppBar, Toolbar, Typography } from "@mui/material";
import styles from "./Header.module.scss";

const Header: React.FC = () => {
  return (
    <div className={styles.root}>
      <AppBar position="static" className={styles.app_bar}>
        <Toolbar className={styles.tool_bar}>
          <Typography variant="h6" className={styles.title}>
            Redux Toolkit Todo
          </Typography>
        </Toolbar>
      </AppBar>
    </div>
  );
};

export default Header;

TaskFormコンポーネント作成

①features/task/taskForm/TaskForm.tsxとTaskForm.module.scssを作成する

features/task/taskForm/TaskForm.tsx
import React from "react";
import styles from "./TaskForm.module.scss";
import { TextField } from "@mui/material";

const TaskForm: React.FC = () => {
  return (
    <div className={styles.root}>
      <form className={styles.form} noValidate autoComplete="off">
        <TextField
          id="outlined-basic"
          label="New Task"
          variant="outlined"
          className={styles.text_field}
        />
      </form>
    </div>
  );
};

export default TaskForm;
features/task/taskForm/TaskForm.module.scss
.root {
  width: 30vw;
  margin-bottom: 20px;

  .form {
    .text_field {
      width: 100%;
    }
  }
}

react-hook-formの導入

features/task/taskForm/TaskForm.tsx
import React from "react";
import { useForm } from "react-hook-form";
import styles from "./TaskForm.module.scss";
import { TextField } from "@mui/material";

type Inputs = {
  taskTitle: string;
};

const TaskForm: React.FC = () => {
  const { register, handleSubmit, reset } = useForm();
  const handleCreate = (data: Inputs) => {
    console.log(data);
    reset();
  };
  return (
    <div className={styles.root}>
      <form onSubmit={handleSubmit(handleCreate)} className={styles.form}>
        <TextField
          id="outlined-basic"
          label="New Task"
          variant="outlined"
          inputRef={register}
          name="taskTitle"
          className={styles.text_field}
        />
      </form>
    </div>
  );
};

export default TaskForm;

TaskItemコンポーネント作成

①features/task/taskItem/TaskItem.tsxとTaskItem.module.scssを作成する

features/task/taskItem/TaskItem.tsx
import React from "react";
import { Checkbox } from "@mui/material";
import { EventNoteIcon, EditIcon, DeleteIcon } from "@mui/icons-material";
import styles from "./TaskItem.module.scss";

interface PropTypes {
  task: { id: number; title: string; completed: boolean };
}

const TaskItem: React.FC<PropTypes> = ({ task }) => {
  return (
    <div className={styles.root}>
      <div className={styles.title}>
        <EventNoteIcon />
        <div className={styles.title_text}>{task.title}</div>
      </div>
      <div className={styles.right_item}>
        <Checkbox
          checked={task.completed}
          onClick={() => console.log(`check${task.id}`)}
          inputProps={{ "aria-label": "primary Checkbox" }}
          className={styles.checkbox}
        />
        <button
          onClick={() => console.log(`edit${task.id}`)}
          className={styles.edit_button}
        >
          <EditIcon className={styles.icon} />
        </button>
        <button
          onClick={() => console.log(`delete${task.id}`)}
          className={styles.delete_button}
        >
          <DeleteIcon className={styles.icon} />
        </button>
      </div>
    </div>
  );
};

export default TaskItem;
features/task/taskItem/TaskItem.module.scss

.wrapper {
    display: flex;
    align-items: center;
    justify-content: space-between;
    border-radius: 5px;
    background-color: #ffffff;
    padding: 10px 20px;
    margin-bottom: 10px;

    .title {
        display: flex;
        align-items: center;

        .title_text {
            margin-left: 10px;
        }
    }

    .right_item {
        display: flex;
        align-items: center;
        
        .edit_button, .delete_button {
            border: none;
            border-radius: 5px;
            background-color: #f1f2f7;
            cursor: pointer;

            .icon {
                width: 20px;
                color: #282828;
            }
        }

        .edit_button {
            margin-right: 5px;
        }
    }
}

.modal {
    display: flex;
    justify-content: center;
    align-items: center;

    .modal_content {
        background: #FFFFFF;
        width: 40vw;
        height: 40vh;
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        border-radius: 5px;
        
        &:focus {
            outline: none;
        }

        .modal_title {
            margin-bottom: 20px;
            font-size: 20px;
        }
    }
}

TaskListコンポーネント作成

①features/task/taskTaskList/TaskList.tsxとTaskList.module.scssを作成する

features/task/taskList/TaskList.tsx
import React from "react";
import styles from "./TaskList.module.scss";
import sampleData from "./sampleData.json";
import TaskItem from "../taskItem/TaskItem";

const TaskList: React.FC = () => {
  return (
    <div className={styles.root}>
      {sampleData.map((task) => (
        <TaskItem key={task.id} task={task} />
      ))}
    </div>
  );
};

export default TaskList;
features/task/taskItem/TaskList.module.scss
.wrapper {
    height: 49vh;
    overflow: hidden;
    overflow-y: auto;
}

Redux側の実装

image.png

①features/task/taskSlice.tsを作成し、
features/counter/coutenrSlice.tsのソースコードをコピペし、編集する。

②actionをexportして、selectTaskの定義する。
③createSliceを用いてsliceの作成する。(name,initialState,reducers)

features/task/taskSlice.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { RootState, AppThunk } from "../../app/store";

//stateの型を記述する
export interface TaskState {
  //taskが何個あるのかを管理する
  idCount: number;
  //storeに保存するtaskの一覧
  tasks: { id: number; title: string; completed: boolean }[];
  //taskのtitleを編集する際にどのtaskが選択されているか
  selectedTasks: { id: number; title: string; completed: boolean };
  //Modalを開くか閉じるかのフラグ
  isModalOpen: boolean;
}
//stateの初期値を記述する
const initialState: TaskState = {
  idCount: 1,
  //taskが何個あるのかを管理する
  tasks: [{ id: 1, title: "Task A", completed: false }],
  //storeに保存するtaskの一覧
  selectedTasks: { id: 0, title: "", completed: false },
  //Modalを開くか閉じるかのフラグ
  isModalOpen: false,
};
//name,initialState,reducersを作成
export const taskSlice = createSlice({
  name: "task",
  initialState,
  reducers: {
    //createReducersのtaskの作成
    createTask: (state, action) => {
      //stateのidCoutを1増やす
      state.idCount++;
      const newTask = {
        id: state.idCount,
        title: action.payload,
        completed: false,
      };
      //state.tasksを更新する
      state.tasks = [newTask, ...state.tasks];
    },
  },
});

export const { createTask } = taskSlice.actions;
//selectedTaskの定義
export const selectedTask = (state: RootState): TaskState["tasks"] =>
  state.task.tasks;

export default taskSlice.reducer;

④app/store.tsを編集する。

app/store.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import counterReducer from "../features/counter/counterSlice";
import taskReducer from "../features/task/taskSlice";

export const store = configureStore({
  reducer: {
    counter: counterReducer,
    //RootStateにaskReducerが含まれる
    task: taskReducer,
  },
});

export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
  ReturnType,
  RootState,
  unknown,
  Action<string>
>;

ReactとReduxの結合

①Task取得機能を実装する。
TaskListでtask一覧をReduxから受け取る。
②sampleData.jsonを削除する。

features/task/taskList/TaskList.tsx
import React from "react";
//react reduxからimportを受ける
import { useSelector } from "react-redux";
//selectTaskをtaskSliceからimport
import { selectTask } from "../taskSlice";
import styles from "./TaskList.module.scss";
//import sampleData from "./sampleData.json";
import TaskItem from "../taskItem/TaskItem";

const TaskList: React.FC = () => {
  //"task"stateをtaskSliceから取得
  const tasks = useSelector(selectTask);
  return (
    <div className={styles.root}>
      {/* //mapで展開 */}
      {tasks.map((task) => (
        <TaskItem key={task.id} task={task} />
      ))}
    </div>
  );
};

export default TaskList;

Task追加機能を実装

①TaskFormでcreateTaskをimportする。

features/task/taskForm/TaskForm.tsx

import React from "react";
import { useForm } from "react-hook-form";
import styles from "./TaskForm.module.scss";
//useDispatchをreact-reduxからimportする
import { useDispatch } from "react-redux";
import { TextField } from "@mui/material";
//TaskFormでcreateTaskをimportする
import { createTask } from "../taskSlice";

type Inputs = {
  taskTitle: string;
};

const TaskForm: React.FC = () => {
  //dispatchを定義する
  const dispatch = useDispatch();
  const { register, handleSubmit, reset } = useForm();
  const handleCreate = (data: Inputs) => {
    //dispatch関数を用いて、createTaskを発火させる  //createTaskの引数として、data.taskTitleを渡す
    dispatch(createTask(data.taskTitle));
    reset();
  };
  return (
    <div className={styles.root}>
      <form onSubmit={handleSubmit(handleCreate)} className={styles.form}>
        <TextField
          id="outlined-basic"
          label="New Task"
          variant="outlined"
          inputRef={register}
          name="taskTitle"
          className={styles.text_field}
        />
      </form>
    </div>
  );
};

export default TaskForm;

参考サイト

【ReduxToolkit入門】#4-1 ~簡易TODOアプリ: Redux Toolkitの理解~
【ReduxToolkit入門】#4-2 ~簡易TODOアプリ: 開発準備編~
【ReduxToolkit入門】#4-3 ~簡易TODOアプリ: UI構築前編~
【ReduxToolkit入門】#4-4 ~簡易TODOアプリ: UI構築後編~
【ReduxToolkit入門】#4-5 ~簡易TODOアプリ: Redux実装前編~
【ReduxToolkit入門】#4-6 ~簡易TODOアプリ: Redux実装中編-1~

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?