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;