티스토리 뷰

 

*피드백은 언제나 환영합니다🙌

 

저번 글에서는 시계 기능을 개발하면서 useEffect와 new Date()에 대해 알게되었다.

아직은 useEffect 쓰는 게 아직 서툴러서 더 공부해야 할 것 같다고 생각했다.

 

 

이번 글에서는 할 일 목록 추가하는 기능을 다뤄보겠다.

 

 

우선 각각의 컴포넌트에서 구현하고 싶은 투두리스트 기능은 다음과 같다.

  • <Today /> (메인페이지): 할 일 추가/수정/삭제/완료 기능
  •  <Tomorrow />: 할 일 추가/삭제 기능
  • <Someday />  : 할 일 추가/삭제 기능

추가/삭제/수정/완료 기능은 <Today /> 컴포넌트에만 넣었고 나머지 컴포넌트는 추가/삭제만 가능하도록 했다. 

 

 

1. 기능 별로 컴포넌트 분리하기


컴포넌트는 다음과 같이 세 가지로 분리했다.

  • TodoInsert.js
  • TodoList.js
  • TodoListItem.js

 

TodoInsert 컴포넌트는 텍스트 입력창과 추가 버튼이 들어갈 부분이다.

TodoList 컴포넌트는 추가된 아이템을 보여주는 부분이다.

TodoListItem 컴포넌트는 추가된 아이템 하나하나를 나타내는 부분이다. (해당 아이템의 완료/삭제/수정 기능을 추가할 것이다.)

 

 

 

 

 

1. TodoInsert.js


먼저 TodoInsert 컴포넌트에 input과 button을 만들었다.

return(
  <div>
    <input placeholder='✔ 할 일 추가' />
    <button>버튼</button>
  </div>
)

 

그리고 스타일링을 해주었다.

TodoInsert 스타일링

버튼에 hover 효과를 넣어 커서를 올리면 헤더 색상과 동일하게 되도록 만들었다.

 

 

 

2. TodoList 


다음으로 input 칸 아래에 표시될 할 일 리스트를 만들어야 한다. 그 전에 Today 컴포넌트에서 TodoList 컴포넌트를 임포트했다.

 

<div className='inputButton'>
    <TodoInsert/>
    <TodoList />
</div>

 

이후 TodoList 컴포넌트에 TodoListItem 컴포넌트를 임포트해주었다.

return(
    <div>
        <TodoListItem />         
    </div>
  )

 

 

 

3. TodoListItem


TodoListItem은 완료/수정/삭제 기능이 필요하다.

그래서 react-icons 에서 MdOutlineCheckBox, MdOutlineCheckBoxOutlineBlank 아이콘과 휴지통 아이콘(FaRegTrashAlt)도 가져왔다.

 

MdOutlineCheckBox, MdOutlineCheckBoxOutlineBlank, FaRegTrashAlt icons

 

 

 

 

완료 / 삭제 아이콘

완료 / 삭제 아이콘을 추가한 모습이다.

중간에 수정 아이콘이랑 제출 버튼도 만들어봤었는데 뭔가 난잡한 느낌이라 지웠다.

 

 

 

수정기능이랑 제출버튼을 어떻게 할까🤔


 

- 검색해보니 제출 버튼은 클릭하는 것 보다 엔터키로 가능하게 하는게 사용자 입장에서 훨씬 편리하다고 한다.

그래서 따로 제출 버튼을 만들지 않고 엔터키로 해결했다.

버튼이 없는 디자인이 나을 것 같아서 다른 방법을 찾아본건데.. 사용자 입장을 고려한 디자인의 중요성도 알게됐다.

 

- 수정은 더블클릭시 수정할 수 있도록 했다. 원래 휴지통 옆에 연필 아이콘 클릭하면 수정할 수 있게 하는게 원래 계획이었다. 그런데 휴지통과 연필이 붙어있어서 실수로 휴지통을 클릭해 할 일이 지워질 수 있을 것 같아 아예 아이콘을 없앴다. 

 

 

 

2. 할 일 목록 추가 기능 만들기


 

먼저 빈 배열을 인자값으로 넣었다. 

여기에는 할 일 목록 객체가 들어가게 된다.

const [todos, setTodos] = useState([]);

 

그리고 Today.js 에 addTodo 함수를 추가한다. 이 함수는 사용자가 입력한 텍스트를 인자로 받아 새로운 todo 객체를 생성한다.

 

id: 숫자 랜덤 생성.
textValue: 사용자가 입력한 텍스트.
checked: 체크 되어있는지 알려줌. false를 기본값으로 설정함.
const addTodo = (text) => {
    setTodos([
      ...todos,
      { id: Math.random().toString(), textValue: text, checked: false },
    ]);
  };

 

그리고 addTodo함수를 props를 이용해 TodoInsert로 전달했다.

<TodoInsert onAddTodo={addTodo} />

 

 

다음으로 엔터키를 누르면 input에 입력한 값이 추가되도록 하는 과정을 다뤄보겠다.

 

useState 훅을 이용하여 사용자가 입력한 텍스트 값의 상태를 관리했다. 텍스트 값은 string(문자열) 이기 때문에 초기 값은 ''으로 정했다. 

function TodoInsert({onAddTodo}) {
  let [newTodoItem, setNewTodoItem] = useState('');
...
}
newTodoItem은 새로 입력한 텍스트의 상태고 setNewTodoItem은 newTodoItem을 업데이트 해준다.

 

이제 실시간으로 사용자가 입력한 텍스트 값의 변화를 관리하기 위해 핸들러 함수(todoInputHandler)를 만들었다.

function todoInputHandler(e) {
    setNewTodoItem(e.target.value);
  };

 

그리고 input의 onChange 안에 넣어주었다. value속성에는 newTodoItem을 넣어서 실시간으로 최신 텍스트가 업데이트 되게했다. 

return(
  <div className='inputButton'>
    <input 
      className='insertBox'
      onChange={todoInputHandler} 
      value={newTodoItem} 
      placeholder='✔ 할 일 추가 (Press Enter)' />
  </div>
  )

 

 

이후 아이템을 추가해주는 핸들러를 만들었다. 이 함수 안에는 onAddTodo와 setNewTodoItem가 있다.

onAddTodo는 사용자가 입력한 텍스트를 전달 받아 목록에 추가하고 setNewTodoItem은 입력창을 공백으로 초기화 시켜준다.

  function addTodoHandler(e) {
    onAddTodo(newTodoItem);
    setNewTodoItem('');
  };

 

마지막으로 엔터키를 누르면 할 일이 추가되도록 input에 onKeyUp 속성을 추가했다.

그리고 &&연산자를 사용하여 엔터키를 누른 경우, e.target.value의 길이가 0보다 크면 아이템이 추가되도록 했다.

따라서 아무것도 입력하지 않았을 경우에는 아이템이 추가되지 않는다.

 

근데 문제가 하나 있다.

다른 텍스트 없이 스페이스를 여러 번 누르고 제출하면 제출되어버린다. 내가 원하는 건 텍스트가 아무것도 없는 경우에는 제출이 안되게 하는 것이다.

아직 방법을 못 찾아서 그냥 onKeyUp을 사용했다. 우선 다른 것 부터 해결하기로!

 

return(
  <div className='inputButton'>
    <input 
      className='insertBox'
      onKeyUp={(e)=> { 
        e.preventDefault();
        if(e.key == 'Enter' && e.target.value.length > 0) {
          addTodoHandler()
        } 
      }} 
      onChange={todoInputHandler} 
      value={newTodoItem} 
      placeholder='✔ 할 일 추가 (Press Enter)' />
  </div>
  )

 

여기까지 TodoInsert 컴포넌트 끝!

import React, {useState} from 'react';


function TodoInsert({onAddTodo}) {
  let [newTodoItem, setNewTodoItem] = useState('');

  function todoInputHandler(e) {
    setNewTodoItem(e.target.value);
  };

  function addTodoHandler(e) {
    onAddTodo(newTodoItem);
    setNewTodoItem('');
  };
  
  return(
  <div className='inputButton'>
    <input 
      className='insertBox'
      onKeyUp={(e)=> { 
       e.preventDefault();
        if(window.event.keyCode == 13 && e.target.value.length > 0) {
              addTodoHandler()
        }
      }} 
      onChange={todoInputHandler} 
      value={newTodoItem} 
      placeholder='✔ 할 일 추가 (Press Enter)' />
  </div>
  )
}

export default TodoInsert;

 

 

 

 

이제 추가한 아이템을 리스트에 출력할 일 만 남았다. Today.js 에서 todos를 TodoList 컴포넌트로 전달했다.

<TodoList todos={todos} />

 

todos는 할 일 목록의 객체가 담긴 배열이다. 따라서 TodoList컴포넌트에서 TodoListItem 컴포넌트로 전달 할 때에는 배열에 담긴 객체 하나하나를 넘겨줘야 한다.

function TodoList({todos}) {
  return(
    <div>
      {
        todos.map(todo => (
            <TodoListItem 
              {...todo} />         
          )
        )
      }
    </div>
  )
}

map()함수로 아이템 하나하나를 TodoListItem 컴포넌트로 전달했다. 그리고 자바스크립트의 디스트럭처링 문법을 이용해서 TodoListItem 컴포넌트에서 아이템 객체에 담긴 값들을 받았다.

 

각각의 아이템에는

  • id
  • textValue
  • checekd

라는 key와 그에 해당하는 value가 담겨있다.

 

따라서 TodoListItem 컴포넌트는 TodoList 컴포넌트에서 전달한 값들을 받을 수 있다.

function TodoListItem({textValue, id, checked}) {
	return (
        .
        .
    <p}>{textValue}</p>
        .
        .
    )
)

 

 

 

 

 


 

이번 문제를 해결하면서 느낀건 대략적인 순서를 정하고 코딩하는 습관이 필요하다는 것이다.

Udemy에서 자바스크립트 강의를 들을 때 강사님이 다이어그램으로 순서도를 짠 뒤 코딩하라고 하셨던게 기억난다.

나는 '일단 해보고 안되면 다른 방법을 생각하자' 주의였는데 이번에 프로젝트를 직접 해보니 무슨 말인지 와닿는다. 앞으로는 계획을 가지고 문제에 뛰어들어야겠다. 

 

오늘은 여기까지!

다음 글에서는 할 일 목록을 삭제하는 기능에 대해 다뤄볼 예정이다.