# 08. TODO APP (Read, Create)

### 들어가기전에

앞서 배운 내용들을 바탕으로 Todo APP 을 만들어보려고 합니다. \
Todo 는 크게 추가, 삭제, 수정, 필터링으로 이루어집니다.\
동작은 [여기](http://todomvc.com/examples/react/#/) 에서 확인 가능합니다.

![](https://707901708-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmntoQaR_UpEAiZTdQt%2F-LondC67HTsF9OIYcg_y%2F-LoneHW0W_KjG0sSAagP%2FKapture%202019-09-15%20at%2015.40.11.gif?alt=media\&token=3b5a1f41-a782-4f1f-8ac4-db55e3b0084a)

### 실습

> 프로젝트는 [초기 세팅 상태](https://simplereact.gitbook.io/simplereact/02.-componet#undefined-1)로 진행되어집니다. 코드량을 최대한 줄이고자 style 은 적용하지 않습니다.

먼저 Todo List 를 가져와 보여주는 부분부터 진행해보겠습니다.

위의 이미지에서 Input 부분이 Header 이고, List 가 표현되는 부분이 Content, 아래 Filter 부분이 Footer 입니다. 크게 3 덩어리로 분리하려고합니다. &#x20;

#### 기본 구성

3개의 Component 를 추가합니다.

```javascript
// src/components/header.js

import React from "react";

function Header() {
  return <div>Header</div>;
}

export default Header;
```

```javascript
// src/components/contents.js

import React from "react";

function Contents() {
  return <div>Contents</div>;
}

export default Contents;
```

```javascript
// src/components/footer.js

import React from "react";

function Footer() {
  return <div>Footer</div>;
}

export default Footer;
```

App.js 에서 가져오겠습니다.

```javascript
// 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 를 이용하는 것 입니다.

```javascript
// 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 에게 전달합니다.

```javascript
// App.js

<Contents todos={todos} />
```

####

#### List 그리기&#x20;

Contents 는 Props 로 Todo List 를 받습니다.

```javascript
// src/components/contents.js 

import React from "react";

function Contents({ todos }) {
  return <div>{JSON.stringify(todos)}</div>;
}

export default Contents;
```

![](https://707901708-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmntoQaR_UpEAiZTdQt%2F-LondC67HTsF9OIYcg_y%2F-LonjS0mnUsPGh8zAMdA%2Fimage.png?alt=media\&token=64fc05d8-ff11-459e-8e66-927becda09fd)

Props 로 넘어오는것을 확인 할 수 있습니다. 이제 받은 데이터를 바탕으로 List 를 그려줍니다.

Todo Component 를 추가합니다. 이 Component 는 Todo 하나에 대한 상태를 업데이트 할 수 있는 Checkbox 와 텍스트를 가지고 있습니다.

```javascript
// 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 를 동시에 호출해서 만들기 때문에 경고가 납니다. 뒤에 샘플 데이터를 제거하면 경고가 사라질거에요

![](https://707901708-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmntoQaR_UpEAiZTdQt%2F-LondC67HTsF9OIYcg_y%2F-LonmMKwOeowVUP-4-eC%2Fimage.png?alt=media\&token=8aab1c39-31ce-4235-a8a4-59ff2d18fdbe)

####

#### Todo 추가&#x20;

이제 Input 에 적은 글을 바탕으로 새로운 Todo 를 만들어내는 것을 해야합니다.\
그 전에 생각해보아야 할 것이 있습니다.&#x20;

Input 은 Header 에 있고 Todo List 의 State 는 App.js 즉 부모에 있습니다.\
\=> 즉, 자식에서 만든 데이터를 부모의 데이터에 추가해야 되는 일이 발생한 것 입니다.\
\=> 이럴때는 보통 데이터를 가지고 있는곳에서 함수를 만들어 자식에게 제공합니다.\
\=> Todo List 를 가진 App.js 에서 Todos 데이터를 다룰 수 있는 함수를 만들어 자식 Header 에게 제공합니다.

App.js 에 handleAdd 라고 불리는 함수를 추가합니다. 해당 함수는 text 를 받아 새로운 Todo 를 만들고 자신이 가진 todos 에 push 해주는 역할을 합니다. 이 함수를 자식 Header 에게 전달합니다.

\=> 보통 내부에서 데이터를 다루는 함수는 handle, 외부에서 넘겨준 event 는 on 이라는 이름을 많이 사용합니다.

```javascript
// 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 에 값에 따라 변경됩니다.

```javascript
// 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;
```

![](https://707901708-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmntoQaR_UpEAiZTdQt%2F-LondC67HTsF9OIYcg_y%2F-LonrDKY3IT1JvpbG4XM%2Fimage.png?alt=media\&token=49e198ae-23b2-427b-a865-533d22560537)

이제 ADD 버튼을 추가하여 해당 버튼이 눌렸을 때 text 의 값을 부모에서 내려받은 onChange 에 전달하면 새로운 Todo 가 추가되는 것 입니다.

```javascript
// 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;

```

![](https://707901708-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmntoQaR_UpEAiZTdQt%2F-LondC67HTsF9OIYcg_y%2F-Lonu8WDyncXQS8zg_ls%2Fimage.png?alt=media\&token=fafb5992-a815-46ab-afb6-fcc1f76693ed)

handleClick 함수를 조금만 수정해주면됩니다.

```javascript
// src/components/header.js

const handleClick = () => {
  onChange(text);
  setText(""); // 추가 후 input 을 비워주기 위해 state 를 초기화 합니다.
};
```

그리고 App.js 에 있는 샘플 데이터를 제거합니다. todos 를 \[] 로 만들어줍니다.

```javascript
// 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;
```

![](https://707901708-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmntoQaR_UpEAiZTdQt%2F-LondC67HTsF9OIYcg_y%2F-Lonv7KwFJOMmYM1jXAk%2FKapture%202019-09-15%20at%2016.56.26.gif?alt=media\&token=ba79a487-c9f2-49c5-8fd6-38ecb9fcceb4)
