ν μ ν¬κΈ°λ‘ μλΌ λ¨Ήλ 리μ‘νΈ μΉμ 9- νλ‘μ νΈ2. ν¬λ리μ€νΈ
<Header/>
<Editor/>
<List/>
λ‘ λλλ€.
mockData μ λΆλ¬μμ§λμ§ νμΈνλ€.

νλͺ©μ μΆκ°νλ©΄ μ½μμ μΆκ°ν κ°μ΄ μ°νλ κ²μ λ³Ό μ μλ€.

λ΄κ° μΆκ°ν κ°κ΅΄μ΄λΌλ νλͺ©μ΄ λ¨λ κ²μ λ³Ό μ μλ€.
νλͺ© λκ° μ΄μλ κ°λ₯νλ€. νμ§λ§ λλ€ λͺ¨λ idκ°μ΄ 0μ΄λ€.
idκ°μ΄ λ€λ₯΄κ² λμ€κ²λ μμ μ΄ νμνλ€!

useRefλ₯Ό μ¬μ©νμ¬ idκ°μ λ€λ₯΄κ² ν΄λ³΄λ©΄,

idλ§λ€ κ³ μ κ°μ κ°μ§λλ‘ λ°λκ²μ νμΈν μ μλ€.
λΉ λ¬Έμμ΄μ μΆκ°νμ¬λ ν¬λκ° μΆκ°λλ λ¬Έμ
Editor.jsx
const onSubmit = () => {
if (content === "") {
return;
}
onCreate(content);
};
μ
λ ₯νΌμ΄ λΉμμ§μ§ μμμ μ¬μ©μκ° λ¬Έμμ΄μ΄ μΆκ°λμλμ§ λͺ¨λ₯Ό μλ μλ€.
Editor.jsx
const onSubmit = () => {
if (content === "") {
contentRef.current.focus();
return;
}
onCreate(content);
setContent(""); //μ
λ ₯ν μ
λ ₯λ μ΄κΈ°ν
};
Enterλ§ λλ¬λ νλͺ©μ΄ μΆκ°λ μ μλλ‘ ν΄μ€ μ μλ€.
onKeyDown={onKeydown}λ₯Ό μ΄μ©νμ¬ μ€μ νλ©΄ λλ€.

Editor.css
.Editor {
display: flex;
gap: 10px;
}
.Editor input {
flex: 1;
padding: 15px;
border: 1px solid rgb(220, 220, 220);
border-radius: 5px;
}
.Editor button {
cursor: pointer;
width: 80px;
border: none;
background-color: rgb(37, 147, 255);
color: white;
border-radius: 5px;
}
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 onKeydown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
const onSubmit = () => {
if (content === "") {
contentRef.current.focus();
return;
}
onCreate(content);
setContent(""); //μ
λ ₯ν μ
λ ₯λ μ΄κΈ°ν
};
return (
<div className="Editor">
<input
ref={contentRef}
value={content}
onKeyDown={onKeydown}
onChange={onChangeContent}
placeholder="μλ‘μ΄ Todo..."
/>
<button onClick={onSubmit}>μΆκ°</button>
</div>
);
};
export default Editor;
Header.css
.Header > h1 {
color: rgb(37, 147, 255);
}
Header.jsx
import "./Header.css";
const Header = () => {
return (
<div className="Header">
<h3>μ€λμ π </h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
};
export default Header; //window + . λ‘ μ΄λͺ¨μ§ μΆκ° κ°λ₯νλ€.
List.css
.List {
display: flex;
flex-direction: column;
gap: 20px;
}
.List > input {
width: 100%;
border: one;
border-bottom: 1px solid rgb(220, 220, 220);
padding: 15px 0px;
}
.List > input:focus {
outline: none;
border-bottom: 1px solid rgb(37, 147, 255);
}
.List .todos_wrapper {
display: flex;
flex-direction: column;
gap: 20px;
}
List.jsx
import "./List.css";
import TodoItem from "./TodoItem";
import { useState } from "react";
const List = ({ todos }) => {
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) => (
<TodoItem key={todo.id} {...todo} />
))}
</div>
</div>
);
};
export default List;
TodoItem.css
.TodoItem {
display: flex;
align-items: center;
gap: 20px;
padding-bottom: 20px;
border-bottom: 1px solid rgb(240, 240, 240);
}
.TodoItem input {
width: 20px;
}
.TodoItem .content {
flex: 1;
}
.TodoItem .date {
font-size: 14px;
color: gray;
}
.TodoItem button {
cursor: pointer;
color: gray;
font-size: 14px;
border: none;
border-radius: 5px;
padding: 5px;
}
TodoItem.jsx
import "./TodoItem.css";
const TodoItem = ({ id, isDone, content, date }) => {
return (
<div className="TodoItem">
<input readOnly checked={isDone} type="checkbox" />
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button> μμ </button>
</div>
);
};
export default TodoItem;
App.css
.App {
width: 500px;
margin: 0 auto;
display: flex;
flex-direction: column;
gap: 10px;
}
App.jsx
import "./App.css";
import { useState, useRef } from "react";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";
const mockData = [
{
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(mockData);
const idRef = useRef(3);
const onCreate = (content) => {
const newTodo = {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
};
setTodos([newTodo, ...todos]);
};
return (
<div className="App">
<Header />
<Editor onCreate={onCreate} />
<List todos={todos} />
</div>
);
}
export default App;
main.jsx
import { createRoot } from "react-dom/client";
import "./index.css";
import App from "./App.jsx";
createRoot(document.getElementById("root")).render(<App />);


λλΆμ νμ μ μ©ν μ 보μ κΉ¨λ¬μ μ»κ³ κ°μ!