본문 바로가기

React

[React] 간단한 투두리스트(todolist) 만들기

투두리스트

 

 

 📌 들어가며

간단한 일정 관리 todolist (투두리스트) 입니다.

할일 추가, 삭제, 체크(할일 다함) 기능이 구현되어 있습니다.

구현하면서 알게된 것들을 정리했습니다.

 

 📌 todolist (투두리스트) 구성

투두리스트의 컴포넌트 구조를 그림으로 그려봤습니다.

App 안에 입력창, 할 일 목록들을 넣었습니다.

JSX 형식으로 표현하면 아래와 같습니다.

[App.js]

<div className="App">
  <TodoItem/> //할일 목록 및 할 일 (ul - li)
</div>

 

 📌 구조 작성해보기

1. input 입력창에서 값을 받아서 업데이트(change)

2. 입력값(value) 받기

3. 등록 버튼 클릭해서 값 저장

4. 입력값을 배열로 저장 (리스트 저장)

5. input 입력값을 어딘가에 뿌려줘 (리스트를 보여줘)

6. 뿌려진 목록에서 원하는 값을 선택 (checkbox) 해서 삭제

 

 📌 입력값 받아서 업데이트 (change)

[App.js]

function App() {
  const [inputValue, setInputValue] = useState(''); // 입력값, 입력값 set
  const [inputList, setInputList] = useState([]); // 입력값 리스트, 입력값 리스트 set

  // 입력값을 setInputValue 으로 set
  const changeTodoInput = (e) => {
    setInputValue (e.target.value);
  }
  
  return (
    <div className="App">
      <div>
        <input type="input" value={inputValue} onChange={changeTodoInput}/>
      </div>
    </div>
  );
}

 

 📌 일정 추가하기 (input value 값 저장)

입력창(Input)에 입력한 값을 useState로 관리하기

[App.js]

function App() {
  
  const [inputValue, setInputValue] = useState('');
  const [inputList, setInputList] = useState([]);
  
  const changeTodoInput = (e) => {
    setInputValue (e.target.value);
  }

  // 할일 추가
  const addTodo = () => {
    setInputList([...inputList, inputValue]); // ...으로 기존값 가져오기, 현재 입력값
    setInputValue(''); // 입력값 초기화 ''
  }

  return (
    <div className="App">
      <div>
        <input type="input" value={inputValue} onChange={changeTodoInput}/>
        <button type="button" onClick={addTodo}>등록</button>
      </div>
    </div>
  );
}

1. onChange 이벤트 : 입력창에 친 값들 추적해서 setInputValue에 저장합니다.

 

 📌 inputList의 값을 화면에 뿌려주기 (input 입력값을 어딘가에 뿌려줘, 리스트를 보여줘)

App.js

function App() {

//  ...

  return (
    <div className="App">
      <div>
        <input type="input" value={inputValue} onChange={changeTodoInput}/>
        <button type="button" onClick={addTodo}>등록</button>
      </div>
      <ul className="content-list">
        {inputList.map((item, i) => {
          return (
            <li>
            <input type="checkbox"/>
              <span>{item}</span>
              <button type="button">삭제</button>
            </li>
          )
        })}
      </ul>
    </div>
  );
}

1. map을 활용하여 inputList 에 있는 item 값들을 화면에 뿌려준다.

 

 📌 삭제하기 (목록에 있는 값 삭제)

function App() {

  // 내가 선택한 item의 index
  const deleteTodo = (index) => {
  // 내가 삭제하고자 하는 item의 index와 inputList에 있는 index(i) 값을 비교
  // ex) 내가 삭제하고자 하는 item은 3번째라면 index 값은 4, 
  // inputList에 있는 index(i) 값은 첫번째줄부터 비교를 시작하기때문에 0부터 시작
  // 4와 0 비교 / 4와 1 비교 / 4와 2 비교 / 4와 3 비교 ....
  // return 에서 true 만 남기는 원리
  // filter 는 true 일 때 값을 item(값)을 반납한다.(return 해준다.)
    setInputList(inputList.filter((item, i) => {
      return index !== i
      
      // 아래 코드는 위 코드 한줄과 동일
      // if (index === i) {
      //   return false
      // } else{
      //   return true
      // }
    }))
  }

  const onClickDelete = (i) => {
    deleteTodo(i)
  }

  return (
    <div className="App">
      <div>
        <input type="input" value={inputValue} onChange={changeTodoInput}/>
        <button type="button" onClick={addTodo}>등록</button>
      </div>
      <ul className="content-list">
        {inputList.map((item, i) => {
          return (
            <li>
            <input type="checkbox"/>
              <span>{item}</span>
              // onClick은 기본적으로 이벤트를 넘겨주는데 onClickDelete에서 필요한 것은 선택한 item의 index 이기 때문에
              // 인라인으로 선언해서 명시적으로 i(index)값을 넣어서 호출한다.
              <button type="button" onClick={(e)=>{onClickDelete(i)}}>삭제</button>
            </li>
          )
        })}
      </ul>
    </div>
  );
}

 

 📌 TodoItem의 컴포넌트화

* 컴포넌트로 빼는 이유 : 

  - 모든 컴포넌트는 재활용성을 향상시키기 위해서

  -(내가 쓰고자 하는 이유) 체크박스 선택시, css 수정을 위해서 독립적으로 랜더링해줄 수 있는 State 값이 필요

    어떤 현상이 발생 하는지?

     -> App 컴포넌트 안에 체크박스 value를 선언하면 checkbox 클릭시, 모든 item 이 선택되는 이슈가 발생함

TodoItem.js

import { useState } from 'react';

// TodoItem === 컴포넌트
function TodoItem ({item, i, deleteTodo}) {
    const [isValidCheck, setIsValidCheck] = useState(false);
    
    const changeCheck = () => {
      setIsValidCheck(!isValidCheck); // 기존 값에 반대
    }

    const onClickDelete = () => {
        deleteTodo(i)
    }
    
    return (
        <li>
            <input type="checkbox" value={isValidCheck} onChange={changeCheck}/>
            <span style={{ textDecoration: isValidCheck?'line-through':'none',color:isValidCheck?'red':'#000' }}>{item}</span>
            <button type="button" onClick={onClickDelete}>삭제</button>
        </li>
    )
}

export default TodoItem;

 

TodoItem을 컴포넌트로 뺐으니 App.js 도 수정

import { useState } from 'react';
// 컴포넌트 추가
import TodoItem from './TodoItem';

function App() {
  
  const [inputValue, setInputValue] = useState('');
  const [inputList, setInputList] = useState([]);
  
  const changeTodoInput = (e) => {
    setInputValue (e.target.value);
  }

  const addTodo = () => {
    setInputList([...inputList, inputValue]);
    setInputValue('');
  }

  const deleteTodo = (index) => {
    console.log(index)
    setInputList(inputList.filter((item, i) => {
      return index !== i
      // if (index === i) {
      //   return false
      // }else{
      //   return true
      // }
    }))
  }

return (
    <div className="App">
      <div>
        <input type="input" value={inputValue} onChange={changeTodoInput}/>
        <button type="button" onClick={addTodo}>등록</button>
      </div>
      <ul className="content-list">
        {inputList.map((item, i) => {
          return (
          // <TodoItem/> -> 컴포넌트 사용법
          // key 는 유일한 값을 넣어준다.
          // item={item} -> property={inputList에 있는 각각의 item}
          // property는 받아주는 값의 이름이다. (컴포넌트에서 가져올 때 쓰는 이름)
          // deleteTodo 는 함수를 가져옴
            <TodoItem key={i} item={item} i={i} deleteTodo={deleteTodo}/>
          )
        })}
      </ul>
    </div>
  );
}

export default App;

 

 

🔮 [ 완성 코드 ] 🔮

[App.js]

import { useState } from 'react';
import TodoItem from './TodoItem';


// 컴포넌트 안하고 내부에서 사용할 때
// TodoItem === 컴포넌트
// function TodoItem ({item, i, deleteTodo}) {
//   const [isValidCheck, setIsValidCheck] = useState(false);
  
//   const changeCheck = () => {
//     setIsValidCheck(!isValidCheck); // 기존 값에 반대
//   }

//   const onClickDelete = () => {
//     deleteTodo(i)
//   }
  
//   return (
//     <li>
//       <input type="checkbox" value={isValidCheck} onChange={changeCheck}/>
//       <span style={{ textDecoration: isValidCheck?'line-through':'none',color:isValidCheck?'red':'#000' }}>{item}</span>
//       <button type="button" onClick={onClickDelete}>삭제</button>
//     </li>
//   )
// }


function App() {
  
  const [inputValue, setInputValue] = useState('');
  const [inputList, setInputList] = useState([]);
  
  const changeTodoInput = (e) => {
    setInputValue (e.target.value);
  }

  const addTodo = () => {
    setInputList([...inputList, inputValue]);
    setInputValue('');
  }

  const deleteTodo = (index) => {
    console.log(index)
    setInputList(inputList.filter((item, i) => {
      return index !== i
      // if (index === i) {
      //   return false
      // }else{
      //   return true
      // }
    }))
  }

  // const onClickDelete = (i) => {
  //   deleteTodo(i)
  // }

  return (
    <div className="App">
      <div>
        <input type="input" value={inputValue} onChange={changeTodoInput}/>
        <button type="button" onClick={addTodo}>등록</button>
      </div>
      <ul className="content-list">
        {inputList.map((item, i) => {
          return (
            <TodoItem key={i} item={item} i={i} deleteTodo={deleteTodo}/>
          )
          // return (
          //   <li>
          //   <input type="checkbox"/>
          //     <span>{item}</span>
          //     <button type="button" onClick={(e)=>{onClickDelete(i)}}>삭제</button>
          //   </li>
          // )
        })}
      </ul>
    </div>
  );
}

export default App;

 

🔮 [ 완성 코드 ] 🔮

[TodoItem.js]

import { useEffect, useState } from 'react';

// TodoItem === 컴포넌트
function TodoItem ({item, i, deleteTodo}) {
    const [isValidCheck, setIsValidCheck] = useState(false);
    

    const changeCheck = () => {
      setIsValidCheck(!isValidCheck); // 기존 값에 반대
    }

    const onClickDelete = () => {
        deleteTodo(i)
    }
    
    return (
        <li>
            <input type="checkbox" value={isValidCheck} onChange={changeCheck}/>
            <span style={{ textDecoration: isValidCheck?'line-through':'none',color:isValidCheck?'red':'#000' }}>{item}</span>
            <button type="button" onClick={onClickDelete}>삭제</button>
        </li>
    )
}

export default TodoItem;