Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,68 @@
# react-todo-list-precourse
# react-todo-list-precourse

# Setting
## App.js
- root를 생성해 Main을 로드합니다.

## Main.jsx
- 생성됨과 동시에 hooks를 로드합니다 by. useTodoState
- UI에 state와 기능들을 전달합니다.




# UI
## SubmitForm
- user에게 submit을 받는 input을 포함한 UI입니다.
- hooks에서 받아온 onSubmit으로 사용자의 submit을 감지하고 item을 추가해 state를 변경하도록 합니다.

## TodoList
- user가 입력한 todo를 화면에 그려줍니다.
- 앞의 체크박스를 체크하면 item의 completed 부분이 true로 변하며 state와 localstorage가 변경됩니다.
- hooks에서 받아온 HandleToggleComplete로 이벤트를 감지하여 style도 취소선으로 변경됩니다.

## Footer
- lists를 확인하고 남은 개수를 보여줍니다.
- hooks에서 받아온 ShowCompletedToggle로 아래의 버튼을 눌렀을 시, condition(all, active, completed)에 따라 해당하는 아이템들만 보여주도록 했습니다.
- Clear Completed 버튼을 눌렀을 시, hooks에서 받아온 ClearCompleted로 state와 localstorage를 변경하였습니다.




# 기능
- Hooks를 통해 State를 관리하고 handlers에 부여해주는 방식으로 구현하였습니다.

## Hooks
### useTodoState
- 전체 아이템들을 관장하는 lists state를 관리합니다.
- item에 랜덤으로 부여되는 NewID를 가지고 있습니다.
- inputRef로 DOM을 관리합니다.
- handlers에게 state나 변수 기능들을 인수로 전달합니다.

## handlers
### HandleSubmit
- submit시 newTodo를 생성하고 input의 value를 받아 todoObject 형식으로 만들어 state와 localstorage에 전달합니다.
- 이후 SubmitForm을 다시 초기화 합니다.

### HandleDelete
- 아이템을 지웠을 시, filter를 거쳐 id와 일치하는 아이템을 지우고 state와 localstorage를 업데이트 합니다.

### HandleToggleComplete
- item 빈배열을 만들고 localstorage의 모든 아이템을 가져와 상태를 확인합니다.
- 클릭된(completed) 아이템만 취소선을 그어줍니다.

### ShowCompletedToggle
- Footer의 버튼들을 클릭하면 해당하는 condition을 받아옵니다.
- condition(all, active, completed)에 따라 보여줄 아이템들을 필터링합니다.
- 필터링한 아이템을 state와 localstorage에 반영합니다.

### ClearCompleted
- 완료된 항목들을 필터링합니다.
- 필터링한(clear)된 항목들을 state에 반영하고 localstorage에서 제거합니다.





# 느낀점
- module로 분리해서 작업하다보니 state 관리로 애를 많이 먹었습니다. 다른 쿠키즈들의 코드를 보면서 공부하여 hooks 라는 새로운 방식이 있음을 알게 되고 활용하여 한 곳에서 state와 DOM, 기능들을 관리하는 법을 배운 것 같아 얻어가는게 많은 과제였습니다.
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<script type="module" src="/src/App.js"></script>
</body>
</html>
21 changes: 21 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
* {
box-sizing: border-box;
margin: 0px;
padding: 0px;
}

.completed{
text-decoration: line-through;
}

.container{
width: 640px;
height: 700px;
display: flex;
flex-direction: column;
position: absolute;
top: 5%;
left: 50%;
/* 화면 끝에서 50% 좌측으로 이동한 것 면적에서 x축기준 -50만큼 이동 */
transform: translateX(-50%);
}
6 changes: 6 additions & 0 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import React from "react";
import ReactDOM from "react-dom/client";
import Main from "./Main";

const root = ReactDOM.createRoot(document.getElementById("app"));
root.render(React.createElement(Main));
26 changes: 26 additions & 0 deletions src/Main.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import "./App.css";
import React from "react";
import useTodoState from "./components/hooks/useTodoState";
import SubmitForm from "./components/ui/SubmitForm";
import TodoList from "./components/ui/TodoList";
import Footer from "./components/ui/Footer";

function Main() {
window.addEventListener("load", () => {
localStorage.clear();
});

const { lists, setLists, inputRef, onSubmit, onDelete, onToggle, showConditionToggle, filteredTodoList } = useTodoState();

return (
<div id="root">
<section className="todoapp container">
<SubmitForm onSubmit={onSubmit} inputRef={inputRef} />
<TodoList lists={lists} HandleToggleComplete={onToggle} HandleDeleteItem={onDelete} />
<Footer ShowCompletedToggle={showConditionToggle} lists={lists} ClearCompleted={filteredTodoList} />
</section>
</div>
);
}

export default Main;
35 changes: 35 additions & 0 deletions src/components/handlers/ClearCompleted.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

const ClearCompleted = (lists, setLists) => {
setLists((prevLists) => {
// 기존 localStorage 데이터를 불러옵니다.
const items = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = JSON.parse(localStorage.getItem(key));
items.push({
id: value.id,
text: value.text,
completed: value.completed,
});
}

// 완료된 항목을 필터링합니다.
const completedItems = items.filter((item) => item.completed);
const newStoredLists = items.filter((item) => !item.completed);

// localStorage에서 완료된 항목을 제거합니다.
completedItems.forEach((item) => {
localStorage.removeItem(item.text);
});

// localStorage에 업데이트된 목록을 저장합니다.
newStoredLists.forEach((item) => {
localStorage.setItem(item.text, JSON.stringify(item));
});

return newStoredLists;
});
};

export default ClearCompleted;
8 changes: 8 additions & 0 deletions src/components/handlers/HandleDeleteItem.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import React from "react";

const HandleDeleteItem = (id, lists, setLists) => {
const filteredItems = lists.filter((item) => item.id !== id);
setLists(filteredItems);
};

export default HandleDeleteItem;
18 changes: 18 additions & 0 deletions src/components/handlers/HandleSubmit.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
const HandleSubmit = (e, NewID, inputRef, lists, setLists) => {
e.preventDefault();

const newTodo = inputRef.current.value;
if (!newTodo.trim()) return; // Avoid empty submissions

const todoObject = { id: NewID(), text: newTodo, completed: false };

localStorage.setItem(newTodo, JSON.stringify(todoObject));

const storageItem = localStorage.getItem(newTodo);
const newItem = JSON.parse(storageItem);

setLists([...lists, newItem]);
inputRef.current.value = "";
};

export default HandleSubmit;
35 changes: 35 additions & 0 deletions src/components/handlers/HandleToggleComplete.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import React from "react";

const HandleToggleComplete = (id, lists, setLists) => {
setLists((prevLists) => {
const updatedLists = prevLists.map((item) =>
item.id === id ? { ...item, completed: !item.completed } : item
);

// 기존 localStorage 데이터를 불러옵니다.
const items = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = JSON.parse(localStorage.getItem(key));
items.push({
id: value.id,
text: value.text,
completed: value.completed,
});
}

// 업데이트된 항목을 반영합니다.
const newStoredLists = items.map((item) =>
item.id === id ? { ...item, completed: !item.completed } : item
);

// localStorage에 업데이트된 목록을 저장합니다.
newStoredLists.forEach((item) => {
localStorage.setItem(item.text, JSON.stringify(item));
});

return updatedLists;
});
};

export default HandleToggleComplete;
37 changes: 37 additions & 0 deletions src/components/handlers/ShowCompletedToggle.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from "react";

const ShowCompletedToggle = (condition, lists, setLists) => {
//condition따라 toggle filter
// localStorage.getItem()

setLists((prevLists) => {
// 기존 localStorage 데이터를 불러옵니다.
const items = [];
for (let i = 0; i < localStorage.length; i++) {
const key = localStorage.key(i);
const value = JSON.parse(localStorage.getItem(key));
items.push({
id: value.id,
text: value.text,
completed: value.completed,
});
}

// 업데이트된 항목을 반영합니다.
const newStoredLists =
condition === "active"
? items.filter((item) => !item.completed)
: condition === "completed"
? items.filter((item) => item.completed)
: items;

// localStorage에 업데이트된 목록을 저장합니다.
newStoredLists.forEach((item) => {
localStorage.setItem(item.text, JSON.stringify(item));
});

return newStoredLists;
});
};

export default ShowCompletedToggle;
33 changes: 33 additions & 0 deletions src/components/hooks/useTodoState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { useState, useRef } from "react";
import HandleSubmit from "../handlers/HandleSubmit";
import HandleDeleteItem from "../handlers/HandleDeleteItem";
import HandleToggleComplete from "../handlers/HandleToggleComplete";
import ClearCompleted from "../handlers/ClearCompleted";
import ShowCompletedToggle from "../handlers/ShowCompletedToggle";

const useTodoState = () => {
const [lists, setLists] = useState([]);
const NewID = () => Math.random().toString(36).substr(2, 16);
const inputRef = useRef();

const onSubmit = (e) => HandleSubmit(e, NewID, inputRef, lists, setLists);
const onDelete = (id) => HandleDeleteItem(id, lists, setLists);
const onToggle = (id) => HandleToggleComplete(id, lists, setLists, NewID);
const showConditionToggle = (condition) =>
ShowCompletedToggle(condition, lists, setLists);
const filteredTodoList = () => ClearCompleted(lists, setLists);

return {
lists,
setLists,
NewID,
inputRef,
onSubmit,
onDelete,
onToggle,
filteredTodoList,
showConditionToggle,
};
};

export default useTodoState;
25 changes: 25 additions & 0 deletions src/components/ui/Footer.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import React from "react";
import styles from './Footer.module.css';

const Footer = ({lists, ShowCompletedToggle, ClearCompleted}) => {
return (
<li className={styles.footer}>
<div className={styles.count}>
<p className={styles.countItem}>{lists.length}</p>
<p>items left</p>
</div>
<div className={styles.buttons}>
<button onClick={() => ShowCompletedToggle("all")}>All</button>
<button onClick={() => ShowCompletedToggle("active")}>Active</button>
<button onClick={() => ShowCompletedToggle("completed")}>
Completed
</button>
</div>
<div className={styles.clear}>
<button onClick={ClearCompleted}>Clear Completed</button>
</div>
</li>
);
};

export default Footer;
60 changes: 60 additions & 0 deletions src/components/ui/Footer.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
.footer{
width: 540px;
height: 51px;
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
padding-left:24px ;
}

.count{
display: flex;
flex-direction: row;
font-family: "Josefin Sans";
color: #9495A5;
font-size: 14px;
}

.buttons{
width: 166px;
height: 14px;
display: flex;
flex-direction: row;
justify-content: space-between;
}

.buttons button{
font-family: "Josefin Sans";
font-weight: 700px;
color: #9495A5;
border:0;
background-color: transparent;
cursor: pointer;
}

.buttons button:hover {
color: #494C6B;
}

.buttons button:active {
color: #3A7CFD;
}

.buttons button:focus {
color: #3A7CFD;
}

.clear button{
border: 0;
background-color: transparent;
cursor: pointer;
margin-right: 24px;
font-family: "Josefin Sans";
font-weight: 350;
}

.clear button:hover{
color: #494C6B;
}

Loading