TodoList 기능 만들기 (23.09.13)

·2023년 9월 13일
0

React

목록 보기
8/30
post-thumbnail

💡 TodoList 기능


📁 VS Code

🔎 TodoList.js

import React, { useState, useContext } from 'react';
import { TodoListContext } from './App';

const TodoList = () => {
    const {setTodoList, loginMember, todoList} = useContext(TodoListContext);

    const [inputTodo, setInputTodo] = useState('');

    let keyIndex = 0;

    // 할 일 추가
    const handleAddTodo = () => {

        // 입력 X
        if(inputTodo.trim().length === 0){
            alert('할 일을 입력해 주세요.');
            return;
        }

        fetch("/todo", {
            method : "POST",
            headers : {"Content-Type" : "application/json"},
            body : JSON.stringify({
                title : inputTodo,
                todoMemberNo : loginMember.todoMemberNo
            })
        })
        .then(resp => resp.text())
        .then(todoNo => {
            if(Number(todoNo) === 0){ // 실패 시 멈춤
                return;
            }

            // 기존 todoList + 새로 추가된 Todo를 이용해
            // 새 배열을 만들어
            // todoList에 대입
            
            // 새로 추가된 Todo 만들기
            const newTodo = {
                todoNo : todoNo,
                title : inputTodo,
                todoMemberNo : loginMember.todoMemberNo,
                isDone : "X"
            };

            // 기존 todoList + newTodo를 이용해 새 배열 만들기
            const newTodoList = [...todoList, newTodo];

            // todoList에 대입
            setTodoList(newTodoList);
            setInputTodo('');

        })
        .catch(e => console.log(e))
    }

    // O,X 업데이트
    const handleToggleTodo = (todo, index) => {
        // console.log(todo);
        // console.log(index);

        fetch("/todo", {
            method : "PUT",
            headers : {"Content-Type" : "application/json"},
            body : JSON.stringify({
                "todoNo" : todo.todoNo,
                "isDone" : todo.isDone === 'O'? 'X' : 'O'
            })
        })
        .then(resp => resp.text())
        .then(result => {
            if(result === '0'){
                console.log('업데이트 실패!');
                return;
            }

            // 수정 성공 시 todoList 값을 변경해서 리렌더링
            
            // todoList를 깊은 복사
            const newTodoList = [...todoList];

            // index번째 요소의 O,X를 반대로 변경
            newTodoList[index].isDone = newTodoList[index].isDone === 'O'? 'X' : 'O';

            setTodoList(newTodoList);

        })
        .catch(e=>console.log(e));
    }

    // 삭제
    const handleDeleteTodo = (todoNo, index) => {
        // console.log(todoNo);
        // console.log(index);

        fetch("/todo", {
            method : "DELETE",
            headers : {"Content-Type" : "application/json"},
            body : todoNo
        })
        .then(resp => resp.text())
        .then(result => {
            if(result === '0'){
                alert("삭제 실패!");
                return;
            }

            const newTodoList = [...todoList]; // 배열 복사

            // 배열.splice(인덱스, 몇칸)
            // -> 배열의 인덱스 번째 요소부터
            //    몇 칸을 잘라내서 반환할지 지정
            // --> 배열에서 잘라내진 구분이 사라짐
            newTodoList.splice(index, 1);
            setTodoList(newTodoList); // 리렌더링

        })
        .catch(e=>console.log(e))

    }

    return(
        <>
            <h1>{loginMember.name}의 Todo List</h1>        

            <div className="todo-container">

                <h3>(Todo) 입력</h3>
                <div>
                    <input type="text" value={inputTodo} onChange={e => setInputTodo(e.target.value)} />
                    <button onClick={handleAddTodo}>Todo 추가</button>
                </div>

                <ul>
                    {/* 배열.map : 기존 배열을 이용해서 새로운 배열 만들기 */}
                    {todoList.map((todo, index) => (
                        <li key={keyIndex++}>
                            <div>
                                <span className={todo.isDone === 'O' ? 'todo-compleate' : ''}> {todo.title} </span>

                                <span>
                                    <button onClick={() => { handleToggleTodo(todo, index) }}>{todo.isDone}</button>
                                    <button onClick={() => { handleDeleteTodo(todo.todoNo, index) }}>삭제</button>
                                </span>
                            </div>
                        </li>
                    ))}
                </ul>

            </div>
        </>
    );
};

export default TodoList;

🔎 App.js

import React, { useState, createContext } from 'react';
import './App.css';

import SignupContainer from './Signup';
import Login from './Login';
import TodoList from './TodoList';

export const TodoListContext = createContext(); // 전역변수 생성

function App() {
  // 회원가입, 로그인, 회원의 Todo List 출력/추가/제거
  const [signupView, setSignupView] = useState(false);

  // 로그인한 회원 정보 저장
  const [loginMember, setLoginMember] = useState(null);

  // 로그인한 회원의 todo-list를 저장
  const [todoList, setTodoList] = useState([]);

  return (
    <TodoListContext.Provider value={ {setTodoList, setLoginMember, loginMember, todoList} }>
      <button onClick={ () => {setSignupView(!signupView)} }>
        { signupView ? ('회원 가입 닫기') : ('회원 가입 열기') }
      </button>

      <div className='signup-wrapper'>
        {/* signupView가 true인 경우에만 회원 가입 컴포넌트 렌더링 */}
        {/* 조건식 && (true인 경우) */}
        {signupView === true && (<SignupContainer/>)}
      </div>

      <h1>Todo List</h1>
      <Login />

      <hr/>
      {/* 로그인이 되어 있을 때만 Todo-List 출력 */}
      { loginMember && (<TodoList/>) }
      
    </TodoListContext.Provider>
  );
}

export default App;

🔎 App.css

...

.todo-container{
  padding: 20px;
  width: 500px;
}


.todo-container li{
  margin-bottom: 10px;
}


.todo-container li > div{
  display: flex;
  justify-content: space-between;
}


.todo-container li > div > span:last-child{
  display: flex;
}


.todo-container li > div button{
  margin: 0 5px;
}


.todo-compleate{
  text-decoration: line-through;
}

📁 Spring

🔎 TodoController.java

...
   @PostMapping("/todo")
   public int insert(@RequestBody Todo todo) {
      return service.insert(todo);
   }
   
   
   @PutMapping("/todo")
   public int update(@RequestBody Todo todo) {
      return service.update(todo);
   }
   
   
   @DeleteMapping("/todo")
   public int delete(@RequestBody int todoNo) {
      return service.delete(todoNo);
   }

🔎 TodoService.java

...
   int insert(Todo todo);

   int update(Todo todo);

   int delete(int todoNo);

🔎 TodoServiceImpl.java

...
   @Transactional(rollbackFor = Exception.class)
   @Override
   public int insert(Todo todo) {
      int result = dao.insert(todo);
      return result > 0 ? todo.getTodoNo() : 0;
   }

   @Transactional(rollbackFor = Exception.class)
   @Override
   public int update(Todo todo) {
      return dao.update(todo);
   }

   @Transactional(rollbackFor = Exception.class)
   @Override
   public int delete(int todoNo) {
      return dao.delete(todoNo);
   }
   

🔎 TodoDao.java

...
   public List<Todo> selectTodoLst(int todoMemberNo){
      return sqlSession.selectList("todoMapper.selectTodoLst", todoMemberNo);
   }

   public int insert(Todo todo) {
      return sqlSession.insert("todoMapper.insert", todo);
   }

   public int update(Todo todo) {
      return sqlSession.update("todoMapper.update", todo);
   }

   public int delete(int todoNo) {
      return sqlSession.delete("todoMapper.delete", todoNo);
      

🔎 todo-mapper.xml

...
   <select id="selectTodoLst" resultMap="todo_rm">
      SELECT * FROM TODO_LIST
      WHERE TODO_MEMBER_NO = #{todoMemberNo}
      ORDER BY 1
   </select>
   
   <insert id="insert" useGeneratedKeys="true">
      
      <selectKey order="BEFORE" keyProperty="todoNo" resultType="_int" >
         SELECT SEQ_TODO_NO.NEXTVAL FROM DUAL
      </selectKey>
      
      INSERT INTO TODO_LIST
      VALUES(${todoNo}, #{title}, default, ${todoMemberNo})
   </insert>
   
   <update id="update">
      UPDATE TODO_LIST SET
      IS_DONE = #{isDone}
      WHERE TODO_NO = ${todoNo}
   </update>
   
   <delete id="delete">
      DELETE FROM TODO_LIST
      WHERE TODO_NO = ${todoNo}
   </delete>
...
  

💻 구현 화면

profile
풀스택 개발자 기록집 📁

0개의 댓글