[ Rudex ] ReactとReduxでTODOアプリ制作！

はじめに
-
学習のアウトプット残します。間違い等あればご指摘をお願いします。
ReactとRedux-toolkit使用してTODOアプリ作成するだけの記事です。

早速、作っていきます！
-
＜機能＞
・テキストエリアの入力したTODOをローカルストレージに保存（初期値にもする）
・TODOの投稿と削除のみ
・Material UI使用　※使い方について説明していません。

＜ディレクトリ構成＞
![redux_ディレクトリ_image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3202621/426a58b6-e2d8-240c-16b0-89fa5cad94e8.png)


#### １．必要なパッケージをインストール

Redux Toolkit と React-Redux パッケージをプロジェクトに追加する。
[Material UIはこちらからインストール](URL "https://mui.com/material-ui/getting-started/installation/")
```
npm install @reduxjs/toolkit react-redux
```



#### 2．ストアを作成して初期値(initialState)と状態を更新する関数(reducer)を準備

１．createSlice関数と使ってreducerとactionを生成する
　　createSliceにオブジェクトとして設定値を与える。
　　・name(actionタイプ)
　　・initialState（reducerの初期値）
　　・reducers（関数）
　　各reducerに渡される引数（ state(現在の状態), action(dispatchで送られてくる値) ）。
```react:todoSlice.js
import { createSlice } from "@reduxjs/toolkit";

//ローカルストレージから既存のTODOリスト配列を取得
const getTodos = JSON.parse(localStorage.getItem("TAKENOKO_TODO"));

//各タスクのIDを採番する関数
const updateId = (arr) => {
  const newObj = new Array();
  arr.forEach((todo, index) => {
    newObj.push({ id: index, task: todo.task });
  });
  return newObj;
};

const todoSlice = createSlice({
  name: "todo",
  initialState: getTodos || [], //初期値にはローカルストレージのデータか空の配列
  reducers: {                   
    //新たにTODOを追加する
    add: (state, action) => {
      const newTodo = [...state, action.payload];
      localStorage.setItem("TAKENOKO_TODO", JSON.stringify(newTodo));
      return newTodo;
    },
    //TODOを削除する
    remove: (state, action) => {
      const todos = Object.values(state).map((item) => ({ ...item }));
      let updateTodo = todos.filter((todo) => todo.id !== action.payload.id);
      updateTodo = updateId(updateTodo);
      localStorage.setItem("TAKENOKO_TODO", JSON.stringify(updateTodo));
      return updateTodo;
    },
  },
});

export const { add, remove } = todoSlice.actions;
export default todoSlice.reducer;
```
　
２．storeを生成してreducerをセットする
　　configureStoreをインポートしてオブジェクトを引数に与える。
　　reducerの設定（複数reducerを設定することができる）。
```react:store.js
import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "../features/todoSlice";

export default configureStore({
  reducer: {
    todos: todosReducer,
  },
});
```
　
３．グローバルに使用可能にする
　　react-reduxからProviderをインポートしてプロジェクト全体を囲む。
　　作成したストアをpropsとして渡す。
```react:main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App.jsx";
import "./index.css";
import "@fontsource/roboto/300.css";
import "@fontsource/roboto/400.css";
import "@fontsource/roboto/500.css";
import "@fontsource/roboto/700.css";
import { Provider } from "react-redux";
import store from "./app/store";

ReactDOM.createRoot(document.getElementById("root")).render(
  <React.StrictMode>
    <Provider store={store}>
      <App />
    </Provider>
  </React.StrictMode>
);
```
　
４．Appコンポーネント作成
　　useState等使用しなくてもどのコンポーネントでも状態管理できるようになります🤤
```react:App.jsx
import TodoInput from "./components/todo/TodoInput";
import TodoList from "./components/todo/TodoList";

function App() {
  return (
    <>
      <h1>TODOアプリ</h1>
      <TodoInput />
      <TodoList />
    </>
  );
}

export default App;
```
　
４．現在のstateの取得と更新方法
　　簡単に使い方説明します。※詳しくは公式ドキュメントを見てください。
　　・useSelecterで現在のstateを取得出来ます。
　　・useDispatchにactionを渡してstoreにdispatch出来ます。
　　　→引数を渡して設定したreducer内で扱えます。
```react:TodoInput.jsx
import { useState } from "react";
import { Button } from "@mui/material";
import { Grid, TextField } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { add } from "../../features/todoSlice";

function TodoInput() {
  const dispatch = useDispatch();
  const todos = useSelector((state) => state.todos);
  const [task, setTask] = useState("");

  //todoを追加するボタン
  const addTodo = () => {
    if (task === "") return;
    const newTodo = { id: todos.length + 1, task: task };
    dispatch(add(newTodo));
    setTask("");
  };

  return (
    <Grid container spacing={5} alignItems="center" justifyContent="center">
      <Grid item>
        <TextField
          value={task}
          type="text"
          margin="dense"
          sx={{
            width: 400,
          }}
          placeholder="タスクを入力しましょう"
          onChange={(e) => setTask(e.target.value)}
        />
      </Grid>
      <Grid item>
        <Button
          onClick={addTodo}
          variant="contained"
          size="large"
          color="secondary"
        >
          追加
        </Button>
      </Grid>
    </Grid>
  );
}

export default TodoInput;
```
　
```react:TodoList.jsx
import { Box, Button, Stack } from "@mui/material";
import { useDispatch, useSelector } from "react-redux";
import { remove } from "../../features/todoSlice";

function TodoList() {
  const todos = useSelector((state) => state.todos);
  const dispatch = useDispatch();

  //todoを削除するボタン
  const deleteTodo = (e, todo) => {
    dispatch(remove(todo));
  };

  return (
    <Stack
      direction="column"
      justifyContent="center"
      alignItems="center"
      spacing={0.5}
      sx={{
        margin: 2,
      }}
    >
      {todos.map((todo) => {
        return (
          <Box
            key={todo.id}
            sx={{
              display: "flex",
              justifyContent: "space-between",
              alignItems: "center",
              p: 1,
              m: 1,
              background:
                "linear-gradient(167deg, rgba(255, 167, 247, 1), rgba(255, 137, 179, 1) 19%, rgba(224, 216, 239, 1) 31%, rgba(216, 237, 255, 1) 61%)",
              borderRadius: 1,
              width: 500,
              boxShadow: "0px 2px 7px 0px rgba(0, 0, 0, 0.35)",
            }}
          >
            <Box>{todo.task}</Box>
            <Button
              onClick={(e) => deleteTodo(e, todo)}
              sx={{
                color: "red",
                background: "pink",
                "&:hover": {
                  background: "rgba(8, 33, 0, 0.26)",
                },
              }}
            >
              削除
            </Button>
          </Box>
        );
      })}
    </Stack>
  );
}
export default TodoList;
```

今後、Reduxで非同期処理行う方法についても記事書いてみたいです。
以上になります。

参考にした教材
-
+ [【Redux入門】初学者でも理解できるReduxの仕組みを解説します！(Redux Toolkitを使用)](URL "https://www.youtube.com/watch?v=KuRu5wOyY_c")
  →ドキュメントをより分かりやすく丁寧に解説してくれました。

+ [公式チュートリアル](URL "https://redux.js.org/tutorials/quick-start")
  →ドキュメント読む練習、必要なパッケージについて、カウンターアプリ作れます。

+ [【2023年最新】React(v18)完全入門ガイド｜Hooks、Next.js、Redux、TypeScript](URL "https://www.udemy.com/course/react-complete-guide/")
→ Reactについても詳しく説明してくれている講座です。
　Reduxの非同期処理、具体的な使い方をハンズオンで学習できます。