[React Hooks] Context API와 useContext

박은지·2022년 2월 20일
0

Todo Application

목록 보기
4/7

1. 컴포넌트 구조화

Header 컴포넌트

App.js에서 제목인 <h1> 태그를 Header 컴포넌트로 이동시킨다.

Form 컴포넌트

시작하기 전에 Form 컴포넌트도 만들어주자.
form 태그 부분만 컴포넌트로 구조화 안된 상태여서 매우 거슬림...

Form.jsx
components 폴더에 Form.jsx 파일을 생성하고 App.js의 form 태그에 대한 코드를 옮긴다.
여기서 addTodochangeInputData 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;

2. Context API 적용하기

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;

[1] Consumer 사용하기

이제 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;

[2] useContext 사용하기

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

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

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;

0개의 댓글