08. TODO APP (Read, Create)
Todo APP 을 만들어봅니다
들어가기전에
앞서 배운 내용들을 바탕으로 Todo APP 을 만들어보려고 합니다. Todo 는 크게 추가, 삭제, 수정, 필터링으로 이루어집니다. 동작은 여기 에서 확인 가능합니다.

실습
프로젝트는 초기 세팅 상태로 진행되어집니다. 코드량을 최대한 줄이고자 style 은 적용하지 않습니다.
먼저 Todo List 를 가져와 보여주는 부분부터 진행해보겠습니다.
위의 이미지에서 Input 부분이 Header 이고, List 가 표현되는 부분이 Content, 아래 Filter 부분이 Footer 입니다. 크게 3 덩어리로 분리하려고합니다.
기본 구성
3개의 Component 를 추가합니다.
// src/components/header.js
import React from "react";
function Header() {
return <div>Header</div>;
}
export default Header;
// src/components/contents.js
import React from "react";
function Contents() {
return <div>Contents</div>;
}
export default Contents;
// src/components/footer.js
import React from "react";
function Footer() {
return <div>Footer</div>;
}
export default Footer;
App.js 에서 가져오겠습니다.
// App.js
import React from "react";
import Header from "./components/header";
import Contents from "./components/contents";
import Footer from "./components/footer";
function App() {
return (
<div>
<Header />
<Contents />
<Footer />
</div>
);
}
export default App;
Todo State 를 추가합니다. Todo 는 텍스트, 상태를 가집니다. 그리고 수정, 삭제시 필요한 고유한 id 를 부여해줘야합니다. 클라이언트에서 고유한 값을 판단하기 가장 쉽고 좋은것은 Date 를 이용하는 것 입니다.
// App.js
import React, { useState } from "react";
import Header from "./components/header";
import Contents from "./components/contents";
import Footer from "./components/footer";
function App() {
// hook 을 이용하여 state 를 다룹니다.
const [todos, setTodos] = useState([
{
id: Date.now(), // 고유한 id
text: "리액트 공부하기", // 텍스트
isDone: false // 완료, 미완료 상태
},
{
id: Date.now(),
text: "밥 먹기",
isDone: true
}
]);
return (
<div>
<Header />
<Contents />
<Footer />
</div>
);
}
export default App;
만든 Todos State 를 해당 내용을 그려줄 Contents 에게 전달합니다.
// App.js
<Contents todos={todos} />
List 그리기
Contents 는 Props 로 Todo List 를 받습니다.
// src/components/contents.js
import React from "react";
function Contents({ todos }) {
return <div>{JSON.stringify(todos)}</div>;
}
export default Contents;

Props 로 넘어오는것을 확인 할 수 있습니다. 이제 받은 데이터를 바탕으로 List 를 그려줍니다.
Todo Component 를 추가합니다. 이 Component 는 Todo 하나에 대한 상태를 업데이트 할 수 있는 Checkbox 와 텍스트를 가지고 있습니다.
// src/components/contents.js
import React from "react";
function Todo({ todo: { id, text, isDone } }) {
return (
<div>
<input type="checkbox" checked={isDone} />
<span>{text}</span>
</div>
);
}
function Contents({ todos }) {
return (
<div>
{todos.map(todo => (
<Todo todo={todo} key={todo.id} />
))}
</div>
);
}
export default Contents;
Checkbox 의 상태는 isDone 에 영향을 받습니다. 여기까지하면 일단 Read 는 다 했습니다. 위 처럼 map 같이 loop 를 돌면서 Component 를 그려줄 때는 항상 고유한 key 값이 필요합니다. => 지금은 샘플 데이터가 Date.now 를 동시에 호출해서 만들기 때문에 경고가 납니다. 뒤에 샘플 데이터를 제거하면 경고가 사라질거에요

Todo 추가
이제 Input 에 적은 글을 바탕으로 새로운 Todo 를 만들어내는 것을 해야합니다. 그 전에 생각해보아야 할 것이 있습니다.
Input 은 Header 에 있고 Todo List 의 State 는 App.js 즉 부모에 있습니다. => 즉, 자식에서 만든 데이터를 부모의 데이터에 추가해야 되는 일이 발생한 것 입니다. => 이럴때는 보통 데이터를 가지고 있는곳에서 함수를 만들어 자식에게 제공합니다. => Todo List 를 가진 App.js 에서 Todos 데이터를 다룰 수 있는 함수를 만들어 자식 Header 에게 제공합니다.
App.js 에 handleAdd 라고 불리는 함수를 추가합니다. 해당 함수는 text 를 받아 새로운 Todo 를 만들고 자신이 가진 todos 에 push 해주는 역할을 합니다. 이 함수를 자식 Header 에게 전달합니다.
=> 보통 내부에서 데이터를 다루는 함수는 handle, 외부에서 넘겨준 event 는 on 이라는 이름을 많이 사용합니다.
// app.js
import React, { useState } from "react";
import Header from "./components/header";
import Contents from "./components/contents";
import Footer from "./components/footer";
function App() {
const [todos, setTodos] = useState([
{
id: Date.now(),
text: "리액트 공부하기",
isDone: false
},
{
id: Date.now(),
text: "밥 먹기",
isDone: true
}
]);
const handleAdd = text => {
setTodos([ // 기존의 데이터와 text 를 바탕으로 만들어진 새로운 todo 를 합칩니다
...todos,
{
id: Date.now(),
text,
isDone: false
}
]);
};
return (
<div>
<Header onChange={handleAdd} />
<Contents todos={todos} />
<Footer />
</div>
);
}
export default App;
이제 Header 를 만들어보겠습니다. Header 는 text 라는 State 를 가지고 있고 이 State 는 Input 에 값에 따라 변경됩니다.
// src/components/header.js
import React, { useState } from "react";
function Header({ onChange }) {
const [text, setText] = useState("");
const handleChange = e => { // input event
setText(e.target.value);
};
return (
<div>
<h1>TODO APP</h1>
<input onChange={handleChange} value={text} />
</div>
);
}
export default Header;

이제 ADD 버튼을 추가하여 해당 버튼이 눌렸을 때 text 의 값을 부모에서 내려받은 onChange 에 전달하면 새로운 Todo 가 추가되는 것 입니다.
// src/components/header.js
import React, { useState } from "react";
function Header({ onChange }) {
const [text, setText] = useState("");
const handleChange = e => {
setText(e.target.value);
};
const handleClick = () => {
console.log("text", text);
};
return (
<div>
<h1>TODO APP</h1>
<input
type="text"
onChange={handleChange}
value={text}
placeholder="오늘은 어떤 일을 하시나요?"
/>
<button onClick={handleClick}>추가</button>
</div>
);
}
export default Header;

handleClick 함수를 조금만 수정해주면됩니다.
// src/components/header.js
const handleClick = () => {
onChange(text);
setText(""); // 추가 후 input 을 비워주기 위해 state 를 초기화 합니다.
};
그리고 App.js 에 있는 샘플 데이터를 제거합니다. todos 를 [] 로 만들어줍니다.
// app.js
import React, { useState } from "react";
import Header from "./components/header";
import Contents from "./components/contents";
import Footer from "./components/footer";
function App() {
const [todos, setTodos] = useState([]); // 샘플 데이터 제거
const handleAdd = text => {
setTodos([
...todos,
{
id: Date.now(),
text,
isDone: false
}
]);
};
return (
<div>
<Header onChange={handleAdd} />
<Contents todos={todos} />
<Footer />
</div>
);
}
export default App;

Last updated
Was this helpful?