[FE] TODO LIST

김주언·2022년 6월 15일
0

TODO LIST

목록 보기
6/18
post-thumbnail

TODO List

프로젝트 구조

.
├── App.css
├── App.js
├── App.test.js
├── app-config.js
├── components
│   ├── AddTodo.jsx
│   └── TodoItem.jsx
├── index.css
├── index.js
├── logo.svg
├── reportWebVitals.js
├── services
│   └── ApiService.js
└── setupTests.js

TodoItem.jsx

App.js에서 props로 전달받는 매개변수들은 아래와 같다.

  • item
  • deleteItem 함수
  • update 함수
// import 생략

const TodoItem = (props) => {
  const [item, setItem] = useState(props.item);

  const [readOnly, setReadOnly] = useState(true);

  const { deleteItem, update } = props;
  const deleteHandler = () => {
    deleteItem(item.id);
  };

  const offReadOnlyMode = () => {
    console.log('Event!', readOnly);
    setReadOnly(false);
  };

  const enterKeyEventHandler = (e) => {
    if (e.key === 'Enter') {
      setReadOnly(true);
      update(item);
    }
  };

  const editEventHandler = (e) => {
    setItem({ ...item, title: e.target.value });
  };

  const checkboxEventHandler = (e) => {
    item.done = e.target.checked;
    setItem(item);
    update(item);
  };

  return (
    <div className="item">
      <input
        type="checkbox"
        name={item.id}
        id={item.id}
        checked={item.done}
        onChange={checkboxEventHandler}
        style={{ marginRight: '5px' }}
      />
      <input
        value={item.title}
        className="item-text"
        id={item.id}
        name={item.id}
        onClick={offReadOnlyMode}
        onChange={editEventHandler}
        onKeyPress={enterKeyEventHandler}
        readOnly={readOnly}
      />

      <button onClick={deleteHandler}>
        <span className="material-icons">highlight_off</span>
      </button>
    </div>
  );
};

export default TodoItem;

  • <input> 체크박스
    - checked는 디폴트로 itme.done이 false이기에 비활성화
    - onChange 속성에 이벤트 핸들러 할당하여 사용자가 체크하면 해당 아이템의 done 속성을 토글한다.
  • <input>
    - 아이템의 title을 출력한다.
    - 클릭 시 readOnly 속성을 false로 바꿔서 수정이 가능하도록 변경
    - onChange 속성에 이벤트 핸들러 할당하여(editEventHandler) 키입력 할 때마다 item을 새 값으로 저장한다.
    - onKeyPress : 엔터 누르면 수정된 내용을 저장한다.

AddTodo.jsx

@material-ui/core 활용하여 스타일링
App.js로부터 props로 add함수를 전달받아서 버튼 클릭 시 호출되도록 한다.

import React from 'react';
import { useState } from 'react';

const AddTodo = (props) => {
  const [inputText, setInputText] = useState('');

  const { add } = props;

  const onInputChange = (e) => {
    setInputText(e.target.value);
  };

  const onButtonClick = () => {
    add(inputText);
    setInputText('');
  };

  const enterEvevtHandler = (e) => {
    if (e.key === 'Enter') {
      onButtonClick();
    }
  };

  return (
    <div className="form">
      <input
        type="text"
        value={inputText}
        onChange={onInputChange}
        onKeyDown={enterEvevtHandler}
      />
      <button onClick={onButtonClick}>
        <span
          style={{
            padding: '0.3rem',
            borderRadius: '5px',
            border: 'solid',
            backgroundColor: 'ButtonFace',
          }}
        >
          ADD
        </span>
      </button>
    </div>
  );
};

export default AddTodo;

사용자 입력 값 저장

  1. 사용자가 할일 추가를 위해 <input> 필드에 title 입력
  2. ADD 버튼 클릭하여 할일 추가

➡️ 사용자가 <input> 필드에 입력한 내용을 저장해야한다.

  • onInputChange
    : input필드에 키를 입력할때마다 실행되고 입력된 문자열을 저장
  • onButtonClick
    : onInputChange에서 저장하고 있던 문자열을 리스트에 추가
  • enterEvevtHandler
    : 엔터키 입력 시 동작하며 onButtonClick 동일한 내용수행

const [inputText, setInputText] = useState('');

state에 사용자 입력값 기억하기 위해 useState 사용한다.

const onInputChange = (e) => {
    setInputText(e.target.value);
  };

// ...
return (
  // ...
 <input
    type="text"
    value={inputText}			// 변경된 상태를 화면에 출력
    onChange={onInputChange}	// 변할때마다 상태 저장
    onKeyDown={enterEvevtHandler}	// 엔터클릭 시 리스트에 추가
  />

add 함수 사용

<button onClick={onButtonClick}>
        <span
          style={{
            padding: '0.3rem',
            borderRadius: '5px',
            border: 'solid',
            backgroundColor: 'ButtonFace',
          }}
        >
          ADD
        </span>
      </button>
    </div>
  );
};

App.js

전체 소스 코드

import React from 'react';
import { useEffect, useState } from 'react';
import TodoItem from './components/TodoItem';
import { Paper, List, Container } from '@material-ui/core';
import AddTodo from './components/AddTodo';
import { call } from './services/ApiService';
const uuid = require('uuid');

const App = () => {
  const [items, setItems] = useState([]);

  useEffect(() => {
    call('/todo', 'GET', null).then((res) => {
      setItems(res.data.data);
    });
  }, []);

  const add = (item) => {
    const id = uuid.v4();
    const newItem = { id, title: item, done: false };
    setItems(items.concat(newItem));
    call('/todo', 'POST', newItem).then((res) => {
      setItems(res.data.data);
    });
  };

  const deleteItem = (item) => {
    const itemId = { id: item };
    call('/todo', 'DELETE', itemId).then((res) => {
      setItems(res.data.data);
    });
  };

  const update = (item) => {
    console.log(item);
    call('/todo', 'PUT', item).then((res) => {
      setItems(res.data.data);
    });
  };

  const todoItems =
    items.length > 0 &&
    items.map((item, idx) => (
      <Paper key={idx}>
        <List key={idx + 1}>
          <TodoItem
            item={item}
            key={item.id}
            deleteItem={deleteItem}
            update={update}
          />
        </List>
      </Paper>
    ));

  return (
    <div>
      <div className="container">
        <div className="heading">
          <h1>TODO LIST</h1>
        </div>
        <AddTodo add={add} />
        {todoItems}
      </div>
    </div>
  );
};

export default App;

useState

컴포넌트의 state에 추가할 todo 아이템을 기억하도록 설정한다.

useEffect

  useEffect(() => {
    call('/todo', 'GET', null).then((res) => {
      setItems(res.data.data);
    });
  }, []);

매개변수로 콜백함수와 리스트를 전달한다.
빈 리스트를 전달하여서 앱이 시작되고 처음 마운트될 때 한 번만 콜백함수를 호출한다.
콜백함수 call은 백엔드 서버의 /todo 경로로 HTTP GET 요청을 보낸다. call의 세번째 매개변수는 옵션인데, GET은 바디를 보내지 않으니 null 설정

call은 Promise를 반환한다. 따라서 .then 메서드 체이닝으로 call함수 실행완료 시 res를 넘겨준다. res는 백엔드서버에 저장되어있는 todo 아이템들이 data속성에 저장되어 있다. 응답으로 받은 todo 아이템들을 프론트 어플리케이션의 state로 넘겨준다.

CRUD 구현

todo 아이템의 기본 구조

let itemStructure = {
  id : "item's id",
  title : "something to do",
  done : false
}

할일 추가 (add)

const add = (item) => {
    const id = uuid.v4();
    const newItem = { id, title: item, done: false };
    setItems(items.concat(newItem));
    call('/todo', 'POST', newItem).then((res) => {
      setItems(res.data.data);
    });
  };

ADD 버튼 클릭 시 실행된다.
매개변수로 사용자가 입력한 todo의 내용(title)을 전달한다. 아이템을 생성하고 기존 state에 저장되어 있던 items(리스트)에 추가해준 뒤, state를 새로 저장한다. 백엔드로 새 아이템을 보내서 데이터베이스에 저장

할일 삭제 (delete)

  const deleteItem = (item) => {
    const itemId = { id: item };
    call('/todo', 'DELETE', itemId).then((res) => {
      setItems(res.data.data);
    });
  };

delete버튼 클릭 시 실행된다.
매개변수로 해당 컴포넌트 오브젝트를 받는다. 오브젝트에서 id를 구조분해하여 추출하고 id를 백엔드 서버로 전송하여 데이터베이스에서 삭제

할일 수정

  1. TodoItem Component의 readOnly 플래그가 false면 수정가능
  2. 사용자가 엔터 누르면 readOnly가 true로 바뀌고 수정 불가
  const update = (item) => {
    console.log(item);
    call('/todo', 'PUT', item).then((res) => {
      setItems(res.data.data);
    });
  };

할일을 클릭하여 수정한 후 엔터키를 누르면 실행된다. 백엔드로 변경된 내용을 보내서 수정한다.


출력

 const todoItems =
    items.length > 0 &&
    items.map((item, idx) => (
      <Paper key={idx}>
        <List key={idx + 1}>
          <TodoItem
            item={item}
            key={item.id}
            deleteItem={deleteItem}
            update={update}
          />
        </List>
      </Paper>
    ));

  return (
    <div>
      <div className="container">
        <div className="heading">
          <h1>TODO LIST</h1>
        </div>
        <AddTodo add={add} />
        {todoItems}
      </div>
    </div>
  );
};

별게없다

profile
학생 점심을 좀 차리시길 바랍니다

0개의 댓글