npx create-react-app todoApp
cd todoApp
위의 과정을 마치면 아래와 같이 많은 폴더와 파일이 생성됩니다.
src 폴더안에서 App.js, App.css, index.js 파일을 제외하고, 나머지 파일은 사용하지 않기 때문에 삭제해줍니다.
App.css 파일안의 내용을 전부 삭제합니다. (초기화)
import './App.css'
import TodoList from './components/TodoList' // 👈
function App() {
return (
<div className="todo-app">
<TodoList></TodoList> // 👈
</div>
)
}
export default App
import React, { useState } from 'react'
import Todo from './Todo'
import TodoForm from './TodoForm'
const TodoList = () => {
const [todos, setTodos] = useState([]) // 👈 useState Hook으로 todos 데이터 저장or조작
// 할 일 "추가"하는 함수
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return
}
const newTodos = [todo, ...todos]
console.log(newTodos)
setTodos(newTodos)
}
// 할 일 "삭제"하는 함수
const removeTodo = id => {
const removedArr = todos.filter(todo => todo.id !== id)
setTodos(removedArr)
}
// 할 일 "수정"하는 함수
const updateTodo = (todoId, newValue) => {
if (!newValue.text || /^\s*$/.test(newValue.text)) {
return
}
setTodos(prev => prev.map(item => (item.id === todoId ? newValue : item)))
}
// 할 일 "완료"하는 함수
const completeTodo = id => {
let updateTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete
}
return todo
})
console.log('complete')
setTodos(updateTodos)
}
return (
<div className="todo-list">
<h1>What's The Plan For Today?</h1>
<TodoForm onSubmit={addTodo}></TodoForm> // 👈 TodoForm 컴포넌트에는 할 일 추가하는 함수 props로 전달합니다.
<Todo // 👈 Todo 컴포넌트에는
todos={todos} // 할 일 데이터
removeTodo={removeTodo} // 제거함수
updateTodo={updateTodo} // 수정함수
completeTodo={completeTodo} // 완료함수를 props로 전달합니다.
></Todo>
</div>
)
}
export default TodoList
import React, { useState } from 'react'
const TodoForm = props => { // props 안에는 객체안에 key값으로 onSubmit 함수가 key 값으로 들어가 있습니다.
const [input, setInput] = useState('') // input에 입력한 값을 저장하기 위해서 useState를 이용해서 input 변수를 생성합니다.
const handleChange = e => {
setInput(e.target.value)
}
const handleSubmit = e => {
e.preventDefault()
props.onSubmit({ // 👈 부모 컴포넌트에서 받아온 함수를 실행합니다. 그러면 인자 값이 부모 컴포넌트로 넘어갑니다. 즉, id, text 값이 넘어갑니다.
id: Math.floor(Math.random() * 10000),
text: input,
})
setInput('') // input 값을 초기화 합니다. <input value={input}/> 태그안의 text를 사라지게 만듭니다.
}
return (
<form className="todo-form" onSubmit={handleSubmit}>
{props.edit ? ( // 👈 props로 받아온 값에서 Key값으로 edit이 true이면 아래의 태그가 랜더링됩니다.
<>
<input
type="text"
placeholder="Update your item"
name="text"
className="todo-input edit"
value={input}
onChange={handleChange}
></input>
<button className="todo-button edit">update</button>
</>
) : ( // props.edit이 false이면 아래의 태그가 랜더링됩니다.
<>
<input
type="text"
placeholder="Add a todo"
name="text"
className="todo-input"
value={input}
onChange={handleChange}
></input>
<button className="todo-button">Add</button>
</>
)}
</form>
)
}
export default TodoForm
npm install react-icons --save
import React, { useState } from 'react'
import TodoForm from './TodoForm'
import { RiCloseCircleLine } from 'react-icons/ri' // react-icons 모듈에 있는 아이콘 가져오기
import { TiEdit } from 'react-icons/ti'
const Todo = ({ todos, removeTodo, updateTodo, completeTodo }) => { // props로 구조 분해 할당으로 받으면 변수처럼 사용할 수 있습니다.
const [edit, setEdit] = useState({
id: null,
value: '',
})
const submitUpdate = newValue => {
updateTodo(edit.id, newValue)
setEdit({
id: null,
value: '',
})
}
if (edit.id) {
return <TodoForm edit={edit} onSubmit={submitUpdate}></TodoForm>
}
return (
<div className="todo">
{todos.map((todo, index) => (
<div
className={todo.isComplete ? 'todo-row complete' : 'todo-row'}
key={index}
>
<div key={todo.id} onClick={() => completeTodo(todo.id)}>
{todo.text}
</div>
<div className="icons">
<RiCloseCircleLine
className="delete-icon"
onClick={() => removeTodo(todo.id)}
></RiCloseCircleLine>
<TiEdit
className="edit-icon"
onClick={() => setEdit({ id: todo.id, value: todo.text })}
></TiEdit>
</div>
</div>
))}
</div>
)
}
export default Todo
/* src / App.css */
* {
padding: 0;
margin: 0;
box-sizing: border-box;
}
html {
margin-top: 70px;
background: rgb(5, 214, 217);
background: linear-gradient(
90deg,
rgba(5, 214, 217, 1) 0%,
rgba(249, 7, 252, 1) 100%
);
}
.todo-app {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
width: 520px;
min-height: 600px;
text-align: center;
background-color: rgba(244, 222, 222, 0.4);
margin: auto;
border-radius: 10px;
padding: 2rem;
}
.todo-list {
display: flex;
flex-direction: column;
}
/*Neon*/
h1 {
margin: 32px 0;
color: #fff;
font-size: 2rem;
line-height: 1;
font-family: Monoton;
animation: neon1 1.5s ease-in-out infinite alternate;
}
/*glow*/
@keyframes neon1 {
from {
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #ff1177,
0 0 70px #ff1177, 0 0 80px #ff1177, 0 0 100px #ff1177, 0 0 150px #ff1177;
}
to {
text-shadow: 0 0 5px #fff, 0 0 10px #fff, 0 0 15px #fff, 0 0 20px #ff1177,
0 0 35px #ff1177, 0 0 40px #ff1177, 0 0 50px #ff1177, 0 0 75px #ff1177;
}
}
.todo-form {
margin-bottom: 32px;
}
.todo-input {
padding: 14px 32px 14px 16px;
border-radius: 4px 0 0 4px;
border: 2px solid rgb(194, 205, 252);
outline: none;
width: 320px;
background-color: transparent;
color: black;
}
.todo-input::placeholder {
color: #e2e2e2;
}
.todo-button {
padding: 16px;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
outline: none;
background: rgb(181, 254, 180);
background: linear-gradient(
90deg,
rgba(181, 254, 180, 1) 0%,
rgba(218, 254, 180, 1) 100%
);
color: darkgray;
text-transform: uppercase;
font-weight: 700;
}
.todo-input.edit {
border: 2px solid #149fff;
}
.todo-button.edit {
background: rgb(131, 58, 180);
background: linear-gradient(
90deg,
rgba(131, 58, 180, 1) 0%,
rgba(253, 29, 29, 1) 50%,
rgba(252, 176, 69, 1) 100%
);
padding: 16px 22px;
}
.todo {
overflow-y: auto;
}
.todo-row {
display: flex;
justify-content: space-between;
align-items: center;
margin: 4px auto;
color: #fff;
background: rgb(229, 189, 246);
background: linear-gradient(
90deg,
rgba(229, 189, 246, 1) 0%,
rgba(216, 222, 222, 1) 100%
);
padding: 15px;
width: 90%;
border-radius: 1rem;
}
.todo-row div:first-child {
font-size: 1.4rem;
}
.icons {
display: flex;
align-items: cent;
font-size: 24px;
cursor: pointer;
color: #fff;
}
.delete-icon {
margin-right: 5px;
}
.complete {
text-decoration: line-through;
opacity: 0.4;
}
배경 이미지
배경 색상
아이콘
데이터 저장