App.js에서 제목인 <h1> 태그를 Header 컴포넌트로 이동시킨다.
시작하기 전에 Form 컴포넌트도 만들어주자.
form 태그 부분만 컴포넌트로 구조화 안된 상태여서 매우 거슬림...
Form.jsx
components 폴더에 Form.jsx 파일을 생성하고 App.js의 form 태그에 대한 코드를 옮긴다.
여기서 addTodo와 changeInputData props를 넘겨받아야 한다는 것을 기억하자.
// Form.jsx
import React from 'react';
function Form({addTodo, changeInputData}) {
return(
<form action="">
<input type="text" name="" onChange={changeInputData}/>
<button onClick={addTodo}>ADD</button>
</form>
);
}
export default Form;
App.js
App.js에는 form 태그에 대한 코드를 <Form addTodo={addTodo} changeInputData={changeInputData} /> Form 컴포넌트로 전환해주고 props를 넘겨준다.
상단에 Form 컴포넌트를 import 해야한다는 것을 기억하자.
// App.js
import React, { useEffect, useState } from 'react';
import './App.css';
// Custom Hook
import useFetch from './useFetch.js';
// Components
import Header from './components/Header.jsx';
import Form from './components/Form.jsx';
import List from './components/List.jsx';
...
return(
<>
<Header todos={todos} />
<Form addTodo={addTodo} changeInputData={changeInputData} />
<List todos={todos} loading={loading} changeTodoDone={changeTodoDone} />
<>
);
}
export default App;
const TodoContext = React.createContext();명령을 통해 createContext를 해주고,
Header, Form, List 컴포넌트들을 <TodoContext.Provider>로 감싸준다.
여기서, const TodoContext = React.createContext(); 를 export하는 이유는 외부 컴포넌트에서 사용해야 하기 때문이다.
App.js
import React, { useEffect, useState } from 'react';
import './App.css';
// Custom Hook
import useFetch from './useFetch.js';
// Components
import Header from './components/Header.jsx';
import Form from './components/Form.jsx';
import List from './components/List.jsx';
// Context API
export const TodoContext = React.createContext();
// App Component
function App() {
const [todos, setTodos] = useState([]); // todos
const [newTodo, setNewTodo] = useState(); // new todos
const loading = useFetch(setTodos, 'http://localhost:4000/initialtodos');
const changeInputData = (e) => { // changeInputData : newTodo에 input에 입력한 내용을 저장하는 함수
setNewTodo(e.target.value);
}
const addTodo = (e) => { // addTodo : 새로운 todo를 배열에 추가하는 함수
e.preventDefault(); // 기본값 form 전송방지
setTodos([...todos, {'title': newTodo, 'todoCode': todos.length, 'contents': '', done: false, edit: false}]);
}
// changeTodoDone
const changeTodoDone = (todoCode) => {
const updateTodos = todos.map(todo => {
if(todo.todoCode === todoCode) {
if(todo.done === true) todo.done = false;
else todo.done = true;
}
return todo;
})
setTodos(updateTodos);
}
useEffect(() => {
console.log("새로운 내용이 추가되었습니다.", todos);
}, [todos]);
return(
<TodoContext.Provider value={{todos}}>
<Header todos={todos} />
<Form addTodo={addTodo} changeInputData={changeInputData} />
<List todos={todos} loading={loading} changeTodoDone={changeTodoDone} />
</TodoContext.Provider>
);
}
export default App;
이제 props를 통해 todos를 전달받지 않고, Context API를 통해 todos 정보를 전달받도록 하자.
Header.jsx
App.js로부터TodoContext를 import하고 Consumer를 통해 todos 정보에 접근할 수 있도록 한다.
// Header.jsx
import React from 'react';
import './Header.css';
import { TodoContext } from '../App.js';
function Header({todos}) {
// 미완료 상태(done: false)인 todo들의 배열
const undoneTasks = todos.filter(todo => todo.done === false);
return(
<TodoContext.Consumer>
{
({todos}) => (
<>
<h1>Todo Application</h1>
<div className='countInfo'>{`남은 할 일 : ${undoneTasks.length}`}</div>
</>
)
}
</TodoContext.Consumer>
);
}
export default Header;
Consumer을 사용해서 todos 정보에 접근할 때 아래의 코드를 반복해서 작성해야 한다는 불편함이 있다.
<TodoContext.Consumer>
{
({todos}) => (
...
)
}
</TodoContext.Consumer>
이러한 번거로움을 없애기 위해 useContext를 사용해보자.
Header.jsx
// Header.jsx
import React, { useContext } from 'react';
import './Header.css';
import { TodoContext } from '../App.js';
function Header() {
// useContext를 통해 todos 정보 접근
const {todos} = useContext(TodoContext);
// 미완료 상태(done: false)인 todo들의 배열
const undoneTasks = todos.filter(todo => todo.done === false);
return(
<>
<h1>Todo Application</h1>
<div className='countInfo'>{`남은 할 일 : ${undoneTasks.length}`}</div>
</>
);
}
export default Header;
나머지 Form 컴포넌트와 List 컴포넌트에도 useContext를 사용해서 todos 정보에 접근할 수 있도록 작업해주자.
App.js
// App.js
import React, { useEffect, useState } from 'react';
import './App.css';
// Custom Hook
import useFetch from './useFetch.js';
// Components
import Header from './components/Header.jsx';
import Form from './components/Form.jsx';
import List from './components/List.jsx';
// Context API
export const TodoContext = React.createContext();
// App Component
function App() {
const [todos, setTodos] = useState([]); // todos
const [newTodo, setNewTodo] = useState(); // new todos
const loading = useFetch(setTodos, 'http://localhost:4000/initialtodos');
const changeInputData = (e) => { // changeInputData : newTodo에 input에 입력한 내용을 저장하는 함수
setNewTodo(e.target.value);
}
const addTodo = (e) => { // addTodo : 새로운 todo를 배열에 추가하는 함수
e.preventDefault(); // 기본값 form 전송방지
setTodos([...todos, {'title': newTodo, 'todoCode': todos.length, 'contents': '', done: false, edit: false}]);
}
// changeTodoDone
const changeTodoDone = (todoCode) => {
const updateTodos = todos.map(todo => {
if(todo.todoCode === todoCode) {
if(todo.done === true) todo.done = false;
else todo.done = true;
}
return todo;
})
setTodos(updateTodos);
}
useEffect(() => {
console.log("새로운 내용이 추가되었습니다.", todos);
}, [todos]);
return(
<TodoContext.Provider value={{todos, addTodo, changeInputData, loading, changeTodoDone}}>
<Header />
<Form />
<List />
</TodoContext.Provider>
);
}
export default App;
Form.jsx
// Form.jsx
import React, { useContext } from 'react';
import { TodoContext } from '../App.js';
function Form() {
const {addTodo, changeInputData} = useContext(TodoContext);
return(
<form action="">
<input type="text" name="" onChange={changeInputData}/>
<button onClick={addTodo}>ADD</button>
</form>
);
}
export default Form;
List.jsx
// List.jsx
import React, { useContext } from 'react';
import { TodoContext } from '../App.js';
import Item from './Item.jsx';
function List() {
const {todos, loading, changeTodoDone} = useContext(TodoContext);
let todoList = <div>Loading...</div>;
if(!loading) todoList = todos.map(todo =>
<Item key={todo.todoCode} todo={todo} changeTodoDone={changeTodoDone} />
);
return(
<ul>
{todoList}
</ul>
);
}
export default List;