環境の準備
ターミナルでreactアプリケーションを作成する。
$ npx create-react-app <プロジェクト名>
% cd <プロジェクト名>
% npm start
必要な環境をインストール
公式サイト:[Redux Toolkit]
(https://redux-toolkit.js.org/)
$ npm install --save @reduxjs/toolkit react-redux
$ npm install react-icons --save
$ npm install react-router-dom
$ npm install --save react-modal
$ npm install react-bootstrap bootstrap@5.1.3
コンポーネント・ファイル構成
src
├── components
├── DisplayTodos.js
├── TodoItem.js
└── Todos.js
├── css
└── main.css
├── redux
├── reducer.js
└── store.js
├── App.js
├── index.js
ベースとなる画面構成を作成する
$ mkdir src/components
$ mkdir src/css
$ mkdir src/redux
$ touch src/components/DisplayTodos.js
$ touch src/components/TodoItem.js
$ touch src/components/Todos.js
$ touch src/css/main.css
$ touch src/redux/store.js
$ touch src/redux/reducer.js
Todoリスト作成
App.jsを編集する
App.js
import "./css/main.css";
import DisplayTodos from "./components/DisplayTodos";
import Todos from "./components/Todos";
function App() {
return (
<div className="App">
<h1>タイトル</h1>
<Todos />
<DisplayTodos />
</div>
);
}
export default App;
index.jsを編集する
index.js
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import { Provider } from "react-redux";
import store from "./redux/store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
src/components/DisplayTodos.js Todos.js TodoItem.jsを編集
src/components/DisplayTodos.js
import React, { useState } from "react";
import { connect } from "react-redux";
import {
addTodos,
completeTodos,
removeTodos,
updateTodos,
} from "../redux/reducer";
import TodoItem from "./TodoItem";
const mapStateToProps = (state) => {
return {
todos: state,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (obj) => dispatch(addTodos(obj)),
removeTodo: (id) => dispatch(removeTodos(id)),
updateTodo: (obj) => dispatch(updateTodos(obj)),
completeTodo: (id) => dispatch(completeTodos(id)),
};
};
const DisplayTodos = (props) => {
const [sort, setSort] = useState("active");
return (
<div className="displaytodos">
<div className="buttons">
<button onClick={() => setSort("active")}>Active</button>
<button onClick={() => setSort("completed")}>Completed</button>
<button onClick={() => setSort("all")}>All</button>
</div>
<ul>
{props.todos.length > 0 && sort === "active"
? props.todos.map((item) => {
return (
item.completed === false && (
<TodoItem
key={item.id}
item={item}
removeTodo={props.removeTodo}
updateTodo={props.updateTodo}
completeTodo={props.completeTodo}
/>
)
);
})
: null}
{props.todos.length > 0 && sort === "completed"
? props.todos.map((item) => {
return (
item.completed === true && (
<TodoItem
key={item.id}
item={item}
removeTodo={props.removeTodo}
updateTodo={props.updateTodo}
completeTodo={props.completeTodo}
/>
)
);
})
: null}
{props.todos.length > 0 && sort === "all"
? props.todos.map((item) => {
return (
<TodoItem
key={item.id}
item={item}
removeTodo={props.removeTodo}
updateTodo={props.updateTodo}
completeTodo={props.completeTodo}
/>
);
})
: null}
</ul>
</div>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(DisplayTodos);
src/components/Todos.js
import React, { useState } from "react";
import { connect } from "react-redux";
import { addTodos } from "../redux/reducer";
import { Button, Navbar } from "react-bootstrap";
import { GoPlus } from "react-icons/go";
const mapStateToProps = (state) => {
return {
todos: state,
};
};
const mapDispatchToProps = (dispatch) => {
return {
addTodo: (obj) => dispatch(addTodos(obj)),
};
};
const Todos = (props) => {
const [todo, setTodo] = useState("");
const handleChange = (e) => {
setTodo(e.target.value);
};
const add = () => {
if (todo === "") {
alert("Input is Empty");
} else {
props.addTodo({
id: Math.floor(Math.random() * 1000),
item: todo,
completed: false,
});
setTodo("");
}
};
return (
<>
<div className="Navbar">
<Navbar bg="green" variant="green">
<Navbar.Brand> login's Dogaben</Navbar.Brand>
<Button type="button" className="btn btn-light">
Logout
</Button>
</Navbar>
</div>
<div className="addTodos">
<input
type="text"
onChange={(e) => handleChange(e)}
className="todo-input"
value={todo}
/>
<button className="add-btn" onClick={() => add()}>
<GoPlus />
</button>
<br />
</div>
</>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(Todos);
src/components/TodoItem.js
import React, { useRef } from "react";
const TodoItem = (props) => {
const { item, updateTodo, removeTodo, completeTodo } = props;
const inputRef = useRef(true);
const changeFocus = () => {
inputRef.current.disabled = false;
inputRef.current.foucus();
};
const update = (id, value, e) => {
if (e.which === 13) {
updateTodo({ id, item: value });
inputRef.current.disabled = true;
}
};
return (
<li key={item.id} className="card">
<textarea
ref={inputRef}
disabled={inputRef}
defaultValue={item.item}
onKeyPress={(e) => update(item.id, inputRef.current.value, e)}
/>
<div className="btns">
<button onClick={() => changeFocus()}>Edit</button>
<button onClick={() => completeTodo(item.id)}>Complete</button>
<button onClick={() => removeTodo(item.id)}>Delete</button>
{""}
</div>
{item.completed && <span className="completed">done</span>}
</li>
);
};
export default TodoItem;
src/redux/reducer.js store.jsを編集
src/redux/reducer.jsを編集する。
src/redux/reducer.js
import { createSlice } from "@reduxjs/toolkit";
const initialState = [];
const addTodoReducer = createSlice({
name: "todos",
initialState,
reducers: {
addTodos: (state, action) => {
state.push(action.payload);
return state;
},
removeTodos: (state, action) => {
return state.filter((item) => item.id !== action.payload);
},
updateTodos: (state, action) => {
return state.map((todo) => {
if (todo.id === action.payload.id) {
return {
...todo,
item: action.payload.item,
};
}
return todo;
});
},
completeTodos: (state, action) => {
return state.map((todo) => {
if (todo.id === action.payload) {
return {
...todo,
completed: true,
};
}
return todo;
});
},
},
});
export const { addTodos, removeTodos, updateTodos, completeTodos } =
addTodoReducer.actions;
export const reducer = addTodoReducer.reducer;
src/redux/store.jsを編集する。
src/redux/store.js
import { configureStore } from "@reduxjs/toolkit";
import { reducer } from "./reducer";
const store = configureStore({
reducer: reducer,
});
export default store;
src/css/main.cssを編集
src/css/main.css
html {
line-height: 1.15;
}
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: linear-gradient(190deg, rgb(55, 216, 68) 0%, rgb(4, 94, 34) 100%);
background-repeat: no-repeat;
background-size: cover;
background-attachment: fixed;
color: white;
overflow: hidden;
}
.App {
margin-top: 0rem;
display: flex;
flex-direction: column;
}
.App h1 {
margin-top: 0rem;
display: inline;
/* text-align: flex;
margin-top: 2rem; */
}
.addTodos {
margin-top: 2rem;
display: flex;
justify-content: center;
}
.todo-input {
min-width: 25rem;
width: 30vw;
max-height: 2.5rem;
background-color: white;
border: none;
border-radius: 5px;
padding: 0.5rem 1rem;
align-self: center;
}
.todo-input:focus {
outline: none;
border: 2px solid darkgreen;
}
.add-btn {
margin-left: 1rem;
background-color: darkgreen;
color: white;
border-radius: 50%;
border: 2.5px solid white;
font-size: 1.5rem;
width: 3.2rem;
height: 3.2rem;
cursor: pointer;
box-shadow: 2px 4px 16px darkgreen;
display: flex;
justify-content: center;
align-items: center;
}
.add-btn:focus {
outline: none;
}
.displaytodos .buttons {
margin-bottom: 2rem;
}
.displaytodos .buttons button {
padding: 0.5rem 1.2rem;
border-radius: 5px;
cursor: pointer;
border: none;
background-color: darkgreen;
color: white;
border: 2px solid white;
}
/* .displaytodos .buttons button :focus {
outline: none;
}
.displaytodos .buttons button :not(:last-child) {
margin-right: 1rem;
} */
.displaytodos ul {
list-style: none;
display: flex;
align-self: flex-start;
flex-wrap: wrap;
margin-left: 0%;
}
.card {
display: flex;
flex-direction: column;
background: white;
background: radial-gradient(circle, white, white 100%);
margin: 0 2rem 1rem 0;
height: 8rem;
width: 16rem;
border-radius: 0.2rem;
padding: 0rem;
position: relative;
}
.card textarea {
padding: 0.5rem;
border-radius: 8px;
border: none;
background-color: #e1ebfd;
color: #271c6c;
height: 70%;
}
.card textarea:disabled {
background-color: transparent;
}
.card .btns {
position: absolute;
bottom: 0rem;
right: 0rem;
}
.card .btns button {
padding: 0.2rem 1.2rem;
border-radius: 5px;
cursor: pointer;
border: none;
background-color: white;
color: darkgreen;
border: 3px solid darkgreen;
/* background-color: darkgreen;
color: white;
border: 2px solid white; */
}
.card .btns button:focus {
outline: none;
}