Language/React.js
[React] TodoList 프로젝트 : 시작
JJcoding
2024. 10. 18. 19:05
간단한 TodoList 프로젝트를 만들어보자.
일단 완성본은?
이렇게 생겼고, 코드를 작성해보자.
Header.jsx
import "./Header.css";
const Header = () => {
return (
<div className="Header">
<h3>오늘은🐿️💛</h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
};
export default Header;
- 가장 상위 컴포넌트로서 제목과 날짜를 표시한다.
Editor.jsx
import "./Editor.css";
import { useState, useRef } from "react";
const Editor = ({ onCreate }) => {
const [content, setContent] = useState("");
const contentRef = useRef();
const onChangeContent = (e) => {
setContent(e.target.value);
};
const onSubmit = () => {
if (content === "") {
contentRef.current.focus();
return;
}
onCreate(content);
setContent("");
};
const onKeydown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
return (
<div className="Editor">
<input
ref={contentRef}
value={content}
onKeyDown={onKeydown}
onChange={onChangeContent}
placeholder="새로운 Todo를 입력하세요."
/>
<button onClick={onSubmit}>추가</button>
</div>
);
};
export default Editor;
- 새로운 할일을 추가하는 컴포넌트
- onKeydown() - 엔터 키로도 추가 되도록 한다.
- 내용이 비어 있으면 추가되지 않고 입력상자에 포커스 되도록 한다.
- 내용을 추가 했으면 입력 상자를 비운다.
List.jsx
import "./List.css";
import TodoItem from "./TodoItem";
import { useState } from "react";
const List = ({ todos, onUpdate, onDelete }) => {
const [search, setSearch] = useState("");
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getFilteredData = () => {
if (search === "") {
return todos;
}
return todos.filter((todo) =>
todo.content.toLowerCase().includes(search.toLowerCase())
);
};
const filteredTodos = getFilteredData();
return (
<div className="List">
<h4>Todo List📝</h4>
<input
value={search}
onChange={onChangeSearch}
placeholder="검색어를 입력하세요."
/>
<div className="todos_wrapper">
{filteredTodos.map((todo) => {
return (
<TodoItem
key={todo.id}
{...todo}
onUpdate={onUpdate}
onDelete={onDelete}
/>
);
})}
</div>
</div>
);
};
export default List;
- filter 사용하여 대소문자 구분 없이 검색 가능하도록 한다.
- map 사용하여 TodoItem 컴포넌트에서 각각의 값을 가져온다.
TodoItem.jsx
import "./TodoItem.css";
const TodoItem = ({ id, isDone, content, date, onUpdate, onDelete }) => {
const onChangeCheckbox = () => {
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
return (
<div className="TodoItem">
<input onChange={onChangeCheckbox} checked={isDone} type="checkbox" />
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
);
};
export default TodoItem;
- 각각의 할일 요소들을(체크박스, 내용, 날짜, 삭제버튼)을 렌더링 하는 컴포넌트
- id를 이용하여 요소마다 삭제, 수정할 수 있다.
App.jsx
import "./App.css";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";
import { useState, useRef } from "react";
const mockDate = [
{
id: 0,
isDone: false,
content: "React 공부하기",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "영어 공부하기",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "독서하기",
date: new Date().getTime(),
},
];
function App() {
const [todos, setTodos] = useState(mockDate);
const idRef = useRef(3);
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
};
const onUpdate = (targetId) => {
// todos State의 값들 중에
// targetId와 일치하는 id를 갖는 Todo Item의 isDone을 변경
// 인수 : todos 배열에서 targetId와 일치하는 id를 갖는 요소의 데이터만 딱 바꾼 새로운 배열
setTodos(
todos.map((todo) => {
// if (todo.is === targetId) {
// return {
// ...todo,
// isDone: !todo.isDone,
// };
// }
// return todo;
return todo.id === targetId ? { ...todo, isDone: !todo.isDone } : todo;
})
);
};
const onDelete = (targetId) => {
// 인수 : todos 배열에서 targetId와 일치하는 id를 갖는 요소만 삭제한 새로운 배열
setTodos(todos.filter((todo) => todo.id !== targetId));
};
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List todos={todos} onUpdate={onUpdate} onDelete={onDelete} />
</div>
);
}
export default App;
- 최상위 부모 컴포넌트로 Editor 컴포넌트와 List 컴포넌트에 props로 변수와 이벤트 핸들러를 보낸다.
다음 포스팅에는 useReducer를 사용해 TodoList를 업데이트 해볼 예정이다.
출처 : 한입 크기로 잘라먹는 리액트