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

## 開発準備

```
$ npx create-react-app <プロジェクト名> --template redux-typescript
# or
$ yarn create react-app <プロジェクト名> --template redux-typescript
```
## formatter等の設定

[PrettierとESLintを基本からまとめてみた【ReactのアプリにESLintとPrettierを導入】](https://qiita.com/kanfutrooper/items/afd44e1a339ff6963ebf)

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

① 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/939638/66c508cd-ded9-ff5a-6f44-972fa01f47aa.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の理解~](https://www.youtube.com/watch?v=yQz2gSQiOZA)
[【ReduxToolkit入門】#4-2 ~簡易TODOアプリ: 開発準備編~](https://www.youtube.com/watch?v=uGVtNuJ9Jvw)
[【ReduxToolkit入門】#4-3 ~簡易TODOアプリ: UI構築前編~](https://www.youtube.com/watch?v=JktfjuUP3eM&t=397s)
[【ReduxToolkit入門】#4-4 ~簡易TODOアプリ: UI構築後編~](https://www.youtube.com/watch?v=xXZVrOf6_OQ&t=852s)
[【ReduxToolkit入門】#4-5 ~簡易TODOアプリ: Redux実装前編~](https://www.youtube.com/watch?v=0ApD0MKXT6I&t=1750s)
[【ReduxToolkit入門】#4-6 ~簡易TODOアプリ: Redux実装中編-1~](https://www.youtube.com/watch?v=s9S-PiQMXsU&t=1100s)