[리액트] 투두리스트 만들기(3) - 완료 편

비얌·2022년 10월 1일
1
post-thumbnail

개요

<지난 포스팅>
리액트로 ToDo앱 만들기(1) - UI 편
리액트로 ToDo앱 만들기(2) - 추가 편

지난 포스팅에서는 ToDo앱 UI 만들기, 투두리스트 추가 기능 만들기를 해보았다.

이번 포스팅에서는 체크박스를 누르면 내용에 중간줄이 그어지면서 완료를 표시하는 기능을 구현해 볼 것이다.

오늘의 목표도 이전과 같이 강의(따라하며 배우는 리액트 A-Z)의 코드를 보지 않고 검색과 혼자의 힘으로 (삽질하며) 투두앱을 만드는 것이다!



완료 표시 기능 만들기

체크박스를 누르면 체크가 표시됨과 동시에 투두리스트 내용에 중간줄이 쳐지는 기능을 구현해 보려고 한다.


1) 체크박스가 클릭되면 이벤트 실행하기

체크박스가 클릭되면 어떤 이벤트가 실행되어야 하는데, 그 이벤트는 체크박스의 내용에 중간줄을 넣는 이벤트여야 한다.

따라서 먼저 체크박스가 클릭되었을 때 checkedToggle이라는 이벤트를 실행되게 했다.

그리고 인자로 todoItem.id를 주었다. 이 아이디를 바탕으로 어떤 요소가 클릭되었는지를 알 수 있기 때문이다.

<input 
  onClick={()=>checkedToggle(todoItem.id)} 
  className="checkbox" 
  type='checkbox'
/>

2) 이벤트 checkedToggle의 내용

checkedToggle 이벤트의 내용은 다음과 같다.

const checkedToggle = (id) => {
  setTodoList(todoList.map(todoItem=>
    todoItem.id === id ? 
    {...todoItem, checked : !todoItem.checked} : todoItem
  ))
}
  • id를 인자로 받아 클릭한 체크박스가 있는 todoItem을 찾고, 그 아이템의 checked를 반대로 바꾼다.

  • 예를 들면 checked: false였다면 checked: true로 바꾸고, checked: true라면 checked: false로 바꾼다.


3) text 부분에 중간줄 긋기

text를 표시하는 부분의 className으로 다음과 같이 주었다.

의미는 todoItem.checked가 true 이면(체크되어 있으면) className을 listContent checked로 하고, todoItem.checked가 false 이면(체크되어 있지 않으면 - 기본값) className을 listContent로 하라는 뜻이다.

<span className={
    `listContent & { todoItem.checked ? 'checked' : '' }`
  }>{todoItem.text}</span>

그리고 todoItem.checked가 true라서 className이 listContent checked라면, App.css에서 아래와 같은 설정을 통해 text에 중간줄이 그어진다.

.checked {
  text-decoration: line-through;
}

하지만..! 동작하지 않는다.

많이 고민해 봤지만,, 강의에서 어떻게 했었는지 보고 익히는 게 효율적일 것 같아 이전에 강의에서 짠 코드를 보기로 했다.



무엇이 잘못됐는가?

바로, className이 잘못됐다!

1을 투두리스트에 입력하고 그 부분을 개발자 도구을 열어서 봐봤다. 그런데, className이 이상한 것이다..! 분명히 listContent checked 혹은 listContent가 class 이름으로 들어와 있어야 했는데, 표현식 자체가 들어와 있었다.


아래와 같이 작성했는데, 백틱(작은따옴표와 비슷하게 생겼고 키보드에서 shift+물결표시를 누르면 나오는 문자)으로 표현식 전체를 감쌈으로써 표현식 자체가 className으로 들어오는 사태가 발생한 것 같다.

<span className={`listContent & 
  { todoItem.checked ? 'checked' : '' }`}>
  {todoItem.text}
</span>

👉 빨간 부분은 사실이 전혀 아님!!!

미래에서 이부분을 공부하고 왔다. 백틱으로 표현식을 감싸서 틀린게 아니라, 오타 때문이었다. &이 아니라 $를 써줘야 한다. 다시한번 이 표현식을 정리하고 가자면, 백틱(``)과 ${} 등은 문자열 안에서 변수를 쓰기 위해 있는 것이다.

`listContent ${ todoItem.checked ? 'checked' : '' }`는 다음과 같이 바꿀 수도 있다. `listContent ${ todoItem.checked && 'checked'}` 이렇게 바꿀 수 있는 이유는 todoItem.checked가 true일 때 checked를 반환하고, false일 때 false를 반환하기 때문이다.

예시는 아래와 같다.

// true와 문자열을 and하면 문자열이 출력됨.
let a = true && "문자열"
console.log(a) // "문자열"

// false와 문자열을 and하면 false가 출력됨
let a = false && "문자열"
console.log(a) // false

// 이유
console.log(Boolean("문자열")) // true

삼항연산자를 사용하는 방법도 있다. 위와 같은 코드를 수정하여 className에 listContent checked 혹은 listContent가 들어오게 만들어보았다. 완성된 코드는 아래와 같다.

<span className={ "listContent" + 
 (todoItem.checked ? " checked" : '')}>
 {todoItem.text}
</span>
  • 삼항연산자를 괄호로 감싸주었다.

  • 필수적으로 있어야 하는 글자인 listContent를 쌍따옴표로 감싸주고 이 와 삼항연산자를 +로 이어주었다.

  • 이때 주의할 사항은 그냥 checked가 아니라 앞에 띄어쓰기 한 칸이 포함된 checked라는 것이다.

  • 띄어쓰기가 없으면 className이 listContentchecked로 설정되는 오류가 발생한다. 이렇게 되면 css에서 checked라는 className을 찾지 못해 체크박스가 선택되어도 중간줄이 그어지지 않는다.


이렇게 하면 다음과 같이 의도했던 대로 className이 만들어진다.


완료 기능 완성!

체크 표시를 누르면(완료하면) 해당 아이템의 내용에 중간줄이 그어지는 기능 구현에 성공했다!


부가적인 기능

완료 표시 기능과 별개로 부가적인 기능을 넣어보았다.

1) 빈칸은 투두리스트에 추가되지 않게 하기

입력할 때 원래 빈칸인 채로 입력 버튼을 눌러도 빈칸이 투두리스트에 올라갔다.

하지만 빈칸을 투두리스트에 추가하는 것은 아무런 의미가 없어 보여서, 추가할 때 내용이면 추가되지 않게 하는 기능을 구현해 보았다. 이렇게 하면 빈칸인 상태에서 입력 버튼을 눌러도 아무런 일이 벌어지지 않는다.

const onSubmit = (e) => {
  e.preventDefault();

  // 이 부분
  if (!text) return;

   const nextTodoList = todoList.concat({
    id: no.current++,
    text,
    checked: false,
  });

  setTodoList(nextTodoList);
  setText('');
};
  • 다만 여기서 주의해야 할 점은 if (!text) return;setTodoList(nextTodoList) 위에 써줘야 한다는 것인데, setTodoList(nextTodoList)를 더 앞에 써서 이 코드를 거치게 되면 이미 투두리스트에 빈칸이 입력되고 난 후이기 때문이다.

2) key 생성하기

어느 날 개발자 도구의 콘솔창에서 이러한 에러가 발생한 것을 발견했다.

검색해보니 리액트에서 map() 메서드를 사용하기 위해서는 배열의 각 Item마다 독립적인 key를 설정해야 한다고 한다. 따라서 key로 독립적인 값을 가지는 id를 할당해 주었다. 이렇게 하면 더 이상 오류가 뜨지 않는다!

<div className='lists'>
  {todoList.map((todoItem) =>(
    // key 설정
    <div className='list' key={todoItem.id}>
      <input 
        onClick={()=>checkedToggle(todoItem.id)} 
        className="checkbox" 
        type='checkbox'
        />
      <span 
        className={ "listContent" + (todoItem.checked ? " checked" : '')}>
        {todoItem.text}
      </span>
      <button type='button' className='deleteBtn'>x</button>
    </div>
  ))}
</div>


🐹 회고

오늘은 투두리스트 완료 표시 기능을 구현해 보았다. 다음 포스팅에서는 삭제 기능을 구현해 볼 것이다!

profile
🐹강화하고 싶은 기억을 기록하고 공유하자🐹

0개의 댓글