on
TodoList CRUD
TodoList CRUD
유튜브 [React로 Todo App 만들기] 보고 만들어보기
App.js
import React, { useState } from "react"; import Template from './components/Template'; import TodoList from './components/TodoList'; import { MdAddCircle } from "react-icons/md"; import TodoInsert from "./components/TodoInsert"; import './App.css' let nextId = 4; const App = () => { const [selectedTodo, setSelectedTodo] = useState(null); const [insertToggle, setInsertToggle] = useState(false); const [todos, setTodos] = useState([ //useState 사용해서 배열 초깃값 설정 { id: 1, text: "할일 1", checked: true }, { id: 2, text: "할일 2", checked: false }, { id: 3, text: "할일 3", checked: true } ]); const onInsertToggle = () => { if(selectedTodo){ setSelectedTodo(null); } setInsertToggle(prev => !prev); }; const onInsertTodo = (text) => { if(text === ""){ return alert("입력해"); } else{ const todo = { id: nextId, text, checked: false }; setTodos(todos => todos.concat(todo)); //원래 배열을 바꾸지 않기 위해 push가 아닌 concat 사용 nextId++; } }; const onCheckToggle = (id) => { setTodos(todos => todos.map(todo => todo.id === id ? {...todo, checked: !todo.checked} : todo)) }; const onChangeSelectedTodo = (todo) => { setSelectedTodo(todo); }; const onRemove = (id) => { onInsertToggle(); setTodos(todos => todos.filter(todo => todo.id !== id)); } const onUpdate = (id, text) => { onInsertToggle(); setTodos(todos => todos.map(todo => todo.id === id ? {...todo, text} : todo)); } return ( {insertToggle && ( )} ); } export default App;
App.css
body{ margin: 0; padding: 0; } .add-todo-button{ position: fixed; right: -5px; bottom: 0; z-index: 100; width: 100px; height: 100px; cursor: pointer; font-size: 5rem; color: #f67280; }
Template.js
import React from "react"; import './Template.css'; const Template = ({children, todoLength}) => { return ( 오늘의 할 일 ({todoLength}) {children} ); } export default Template;
Template.css
.Template{ padding-top: 20px; max-height: 100vh; } .title{ width: 90vw; margin-left: auto; margin-right: auto; padding-bottom: 20px; font-size: 1.5rem; font-weight: bold; color: #6c567b; }
TodoList.js
import React from "react"; import TodoItem from './TodoItem' import './TodoList.css'; const TodoList = ({ todos, onCheckToggle, onInsertToggle , onChangeSelectedTodo }) => { //todos는 할 일 객체가 들어있는 배열 return( {todos.map(todo => ( //map함수를 이용해서 보여준다. //todos 안에 들어있는 text를 감싸서 보여줌 ))} ); } export default TodoList;
TodoList.css
.TodoList{ width: 90vw; margin-left: auto; margin-right: auto; padding-bottom: 20px; }
TodoItem.js
import React from "react"; import {MdCheckBox, MdCheckBoxOutlineBlank, MdCheck} from 'react-icons/md' import './TodoItem.css'; const TodoItem = ({todo, onCheckToggle, onInsertToggle, onChangeSelectedTodo }) => { const { id, text, checked } = todo; //객체 구조 분해를 이용해서 todo객체에서 text를 가져와서 보여줌. return ( {checked ? ( { onCheckToggle(id); }} /> ) : ( { onCheckToggle(id); }} /> )} { onChangeSelectedTodo(todo); onInsertToggle(); }} > {text} ); } export default TodoItem;
TodoItem.css
.TodoItem{ margin-left: auto; margin-right: auto; border-radius: 5px; box-shadow: 1px 2px 5px 1px #f67280; padding: 1rem; display: flex; align-items: center; } .TodoItem + .TodoItem{ margin-top: 15px; } .content{ cursor: pointer; flex: 1; display: flex; align-items: center; } .content svg{ font-size: 1.5em; color: #f67280; } .content .text{ margin-left: 0.5em; flex: 1; } .content.checked .text{ color: #6c567b; text-decoration: line-through; cursor: pointer; opacity: 0.6; }
TodoInsert.js
import React ,{ useState, useEffect } from "react"; import { MdAddCircle, MdOutlineInsertInvitation } from "react-icons/md"; import { TiTrash, TiPencil } from "react-icons/ti"; import './TodoInsert.css'; const TodoInsert = ({ onInsertToggle, onInsertTodo, selectedTodo, onRemove, onUpdate }) => { const [value, setValue] = useState(""); const onChange = (e) => { setValue(e.target.value); }; const onSubmit = (e) => { e.preventDefault(); onInsertTodo(value); setValue(""); onInsertToggle(); }; useEffect(() => { if (selectedTodo) { setValue(selectedTodo.text); } }, [selectedTodo]); return ( { onUpdate(selectedTodo.id, value); } : onSubmit } > {selectedTodo ? ( { onUpdate(selectedTodo.id, value); }} /> { onRemove(selectedTodo.id); }} /> ) : ( )} ); }; export default TodoInsert;
TodoInsert.css
.background{ position: fixed; z-index: 980; top: 0; overflow: hidden; width: 100vw; height: 100vh; display: flex; justify-content: center; align-items: center; background: #6c567b; opacity: 0.8; } form{ margin-left: 10%; position: fixed; top: 40%; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 990; width: 300px; height: 150px; border-radius: 5px; box-shadow: 1px 2px 5px 1px #f67280; background: white; } form > input{ background: none; outline: none; border: none; border-bottom: 1px solid #f67280; padding: 0.5rem; font-size: 1.125rem; line-height: 1.5; } form > button { padding-top: 20px; background: none; outline: none; border: none; color: #f67280; padding-left: 1rem; padding-right: 1rem; font-size: 1.5rem; display: flex; align-items: center; cursor: pointer; transition: 0.1s background ease-in; } .rewrite > svg { padding-top: 20px; color: #f67280; padding-left: 1rem; padding-right: 1rem; font-size: 1.5rem; }
Index.js
from http://jieunny.tistory.com/18 by ccl(A) rewrite - 2021-12-21 20:01:00