はじめに
学習のアウトプット残します。間違い等あればご指摘をお願いします。
ReactとRedux-toolkit使用してTODOアプリ作成するだけの記事です。
早速、作っていきます!
<機能>
・テキストエリアの入力したTODOをローカルストレージに保存(初期値にもする)
・TODOの投稿と削除のみ
・Material UI使用 ※使い方について説明していません。
1.必要なパッケージをインストール
Redux Toolkit と React-Redux パッケージをプロジェクトに追加する。
Material UIはこちらからインストール
npm install @reduxjs/toolkit react-redux
2.ストアを作成して初期値(initialState)と状態を更新する関数(reducer)を準備
1.createSlice関数と使ってreducerとactionを生成する
createSliceにオブジェクトとして設定値を与える。
・name(actionタイプ)
・initialState(reducerの初期値)
・reducers(関数)
各reducerに渡される引数( state(現在の状態), action(dispatchで送られてくる値) )。
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;
2.storeを生成してreducerをセットする
configureStoreをインポートしてオブジェクトを引数に与える。
reducerの設定(複数reducerを設定することができる)。
import { configureStore } from "@reduxjs/toolkit";
import todosReducer from "../features/todoSlice";
export default configureStore({
reducer: {
todos: todosReducer,
},
});
3.グローバルに使用可能にする
react-reduxからProviderをインポートしてプロジェクト全体を囲む。
作成したストアをpropsとして渡す。
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>
);
4.Appコンポーネント作成
useState等使用しなくてもどのコンポーネントでも状態管理できるようになります🤤
import TodoInput from "./components/todo/TodoInput";
import TodoList from "./components/todo/TodoList";
function App() {
return (
<>
<h1>TODOアプリ</h1>
<TodoInput />
<TodoList />
</>
);
}
export default App;
4.現在のstateの取得と更新方法
簡単に使い方説明します。※詳しくは公式ドキュメントを見てください。
・useSelecterで現在のstateを取得出来ます。
・useDispatchにactionを渡してstoreにdispatch出来ます。
→引数を渡して設定したreducer内で扱えます。
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;
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を使用)
→ドキュメントをより分かりやすく丁寧に解説してくれました。 -
公式チュートリアル
→ドキュメント読む練習、必要なパッケージについて、カウンターアプリ作れます。 -
【2023年最新】React(v18)完全入門ガイド|Hooks、Next.js、Redux、TypeScript
→ Reactについても詳しく説明してくれている講座です。
Reduxの非同期処理、具体的な使い方をハンズオンで学習できます。