todolist

김예린·2024년 1월 22일
0
import { useState } from "react";
import "./App.css";

function App() {
  const initialState = {
    id: 0,
    title: "",
    body: "",
    isDone: false,
  };
  const [todos, setTodos] = useState([]);
  const [title, setTitle] = useState("");
  const [body, setBody] = useState("");

  return (
    <div className="container">
      <h2>My Todo List</h2>
      <div className="form-container">
        <form
          onSubmit={(e) => {
            e.preventDefault();
            const newTodos = {
              id: todos.length + 1,
              title: title,
              body: body,
              isDone: false,
            };
            setTodos([...todos, newTodos]);
            setTitle("");
            setBody("");
          }}
        >
          <strong>제목:</strong>
          <input
            value={title}
            placeholder="제목을 입력해주세요"
            onChange={function (e) {
              setTitle(e.target.value);
            }}
            required
          />
          <strong>내용:</strong>
          <input
            value={body}
            placeholder="할 일을 입력해주세요"
            onChange={function (e) {
              setBody(e.target.value);
            }}
            required
          />
          <button className="addBtn">추가하기</button>
        </form>
      </div>

      <div>
        <h2>Working..</h2>
        <div className="workingTodo">
          {todos
            .filter((todo) => todo.isDone === false)
            .map((todo) => {
              return (
                <div key={todo.id} className="work">
                  <h3>{todo.title}</h3>
                  <p>{todo.body}</p>

                  <div className="button">
                    <button
                      onClick={() => {
                        const updateTodos = todos.map((item) => {
                          if (item.id === todo.id) {
                            return {
                              ...item,
                              isDone: !item.isDone,
                            };
                          } else {
                            return item;
                          }
                        });
                        setTodos(updateTodos);
                      }}
                    >
                      완료
                    </button>
                    <button
                      onClick={() => {
                        const deletedTodos = todos.filter((item) => {
                          return item.id !== todo.id;
                        });
                        setTodos(deletedTodos);
                      }}
                    >
                      삭제하기
                    </button>
                  </div>
                </div>
              );
            })}
        </div>
      </div>

      <div>
        <h2>Done..!</h2>
        <div className="workingTodo">
          {todos
            .filter((todo) => todo.isDone === true)
            .map((todo) => {
              return (
                <div key={todo.id} className="work">
                  <h3>{todo.title}</h3>
                  <p>{todo.body}</p>

                  <div className="button">
                    <button
                      onClick={() => {
                        const updateTodos = todos.map((item) => {
                          if (item.id === todo.id) {
                            return {
                              ...item,
                              isDone: !item.isDone,
                            };
                          } else {
                            return item;
                          }
                        });
                        setTodos(updateTodos);
                      }}
                    >
                      취소
                    </button>
                    <button
                      onClick={() => {
                        const deletedTodos = todos.filter((item) => {
                          return item.id !== todo.id;
                        });
                        setTodos(deletedTodos);
                      }}
                    >
                      삭제하기
                    </button>
                  </div>
                </div>
              );
            })}
        </div>
      </div>
    </div>
  );
}

export default App;

코드가 너무 많이 복잡한 것 같다..
함수는 한번에 정리해서 이름만 쓰고 싶은데....
그렇게 하니까 잘 안된다...
전체적인 로직을 이해를 못하고 있는건가...
다시 해봐야지...

컴포넌트 분리하기
scr->components폴더만들기
components->TodoController, TodoForm, TodoList, TodoItem

app.js에서는 TodoController 만 쓸거다!!

TodoController.jsx

전반적인 틀을 담당한다.

return (
    <div className="container">
      <h2>My Todo List</h2>
      <TodoForm formSubmitHandler={formSubmitHandler} />
      <TodoList
        headtitle="Working!"
        todos={workingTodos}
        onDeleteTodoItem={onDeleteTodoItem}
        onToggleTodoItem={onToggleTodoItem}
      />
      <TodoList
        headtitle="Done!"
        todos={doneTodos}
        onDeleteTodoItem={onDeleteTodoItem}
        onToggleTodoItem={onToggleTodoItem}
      />
    </div>
  );

이런식으로 TodoForm, TodoList->TodoItem 으로 값을 내려준다!!
TodoForm, TodoList->TodoItem 에서 todos useState(할일 객체를 수정하는 todos 스테이트)는 상속? 받아서 써야하기 때문에 상위 컴포넌트인 TodoController에 정의한다!!
-> state는 상위에서 하위로 흐르는 단방향이기 때문!

const [todos, setTodos] = useState([
    {
      id: 0,
      title: "리액트",
      body: "리액트 공부하기",
      isDone: false,
    },
  ]);

{}안에 있는거는 걍 초기값임. 안넣어도됌

<TodoForm formSubmitHandler={formSubmitHandler} />

TodoForm에 formSubmitHandler함수를 넘겨줄거다!! 라는 의미이다. 그럼 여기 파일에 formSubmitHandler가 있는거겠지?

const formSubmitHandler = (nextTodo) => {
    setTodos((prevTodos) => [...prevTodos, nextTodo]);
  };

TodoForm.jsx

아래는 TodoForm.jsx이다. title과 body state는 form안에서만 쓰는 것 이기 때문에 여기서 정의했다 !
부모에서 내려준 formSubmitHandler를 받아서 쓰겠다는 의미.

import { useState } from "react";

const TodoForm = ({ formSubmitHandler }) => {
  const [title, setTitle] = useState("");
  const [body, setBody] = useState("");

  const handleSubmit = (e) => {
    e.preventDefault();
    formSubmitHandler({
      id: crypto.randomUUID(),
      title: title,
      body: body,
      isDone: false,
    });

    setTitle("");
    setBody("");
  };

  return (
    <div className="form-container">
      <form onSubmit={handleSubmit}>
        <strong>제목 :</strong>
        <input
          value={title}
          placeholder="제목을 입력해주세요"
          onChange={(e) => {
            setTitle(e.target.value);
          }}
        />
        <strong> 내용:</strong>
        <input
          value={body}
          placeholder="할 일을 입력해주세요"
          onChange={(e) => {
            setBody(e.target.value);
          }}
        />
        <button className="addBtn">추가하기</button>
      </form>
    </div>
  );
};
export default TodoForm;
formSubmitHandler({
      id: crypto.randomUUID(),
      title: title,
      body: body,
      isDone: false,
    });

이것과

const formSubmitHandler = (nextTodo) => {
    setTodos((prevTodos) => [...prevTodos, nextTodo]);
  };

이것을 보자 nextTodo가 폼에서 쓴 아이디, 제목, 내용, 이즈던이 담겨있는것이다. 이전의 투두들을 spread한 후(불변성 때문), 폼에서 넘겨준 nextTodo를 합쳐서 Todos를 업데이트하겠단 의미!
이제 폼은 끝
투두리스트보자

TodoList.jsx

import TodoItem from "./TodoItem";

const TodoList = ({ headtitle, todos, onDeleteTodoItem, onToggleTodoItem }) => {
  return (
    <div>
      <h2>{headtitle}</h2>
      <div className="workingTodo">
        {todos.map((todo) => {
          return (
            <TodoItem
              key={todo.id}
              todo={todo}
              onDeleteTodoItem={onDeleteTodoItem}
              onToggleTodoItem={onToggleTodoItem}
            />
          );
        })}
      </div>
    </div>
  );
};
export default TodoList;

아래는 컨트롤러에서 TodoList에 인자 넘겨주는 부분임

<TodoList
        headtitle="Working!"
        todos={workingTodos}
        onDeleteTodoItem={onDeleteTodoItem}
        onToggleTodoItem={onToggleTodoItem}
      />
      <TodoList
        headtitle="Done!"
        todos={doneTodos}
        onDeleteTodoItem={onDeleteTodoItem}
        onToggleTodoItem={onToggleTodoItem}
      />

headtitle, todos, onDeleteTodoItem, onToggleTodoItem을 넘겨준다!
보면은 todos에는 각각 {workingTodos}와 {doneTodos}가 담겨있는데,

const workingTodos = todos.filter((todo) => !todo.isDone);
const doneTodos = todos.filter((todo) => todo.isDone);

todos의 완료여부에 따라 필터링을 한 값이다.
그러면 workingTodos에는 아직 끝나지 않은(false)인 할일 목록들이 들어있고. doneTodos에는 완료된(true) 할일 목록들이 들어있다! 그것을 TodoList에 넘겨주는 것임.
headtitle에는 각각 부여한 값이 들어가고, 각각의 목록들을 map함수로 보여준다. 그때!!!! TodoItem등장

TodoItem.jsx

const TodoItem = ({ todo, onDeleteTodoItem, onToggleTodoItem }) => {
  const { id, title, body, isDone } = todo;
  return (
    <div className="work">
      <h3>{title}</h3>
      <p>{body}</p>
      <div className="button">
        <button onClick={() => onDeleteTodoItem(id)}>삭제</button>
        <button onClick={() => onToggleTodoItem(id)}>
          {isDone ? "취소" : "완료"}
        </button>
      </div>
    </div>
  );
};
export default TodoItem;

할일 목록들의 안의 내용물을 담당하는 컴포넌트이다.
부모 컴포넌트인 TodoList에서 넘겨받은 것들 사용함
제목과 내용을 표시해야하고, isDone여부에 따라 완료와 취소버튼을 다르게 해야하기 때문에 todo를 구조분해할당한다!
그냥 투두객체에 있는 값들 쓰겠다는 의미
제목, 내용 넣어주고!!
삭제 버튼은 완료여부상관없이 선택된거 지우면 되니까는 지우는 함수만 onclick에 넣어주고, isDone이 true(완료된 할 일)면 취소버튼으로, false(완료되지 않은 할 일)이면 완료버튼으로 해준다! 그리고 onToggleTodoItem함수를 달아준다!
이제 그 지우고 토글하는 함수들을 자세히 살펴보자!

const onDeleteTodoItem = (id) => {
    setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
  };

먼저 할일을 지우는 함수는
id를 넘겨줘야한다! TodoItem에서 버튼을 누른 할일목록의 id를 넘겨준다(선택된 것만 지워야하니까!)
원래 있던 할일목록들을 필터할거다. 우리가 삭제버튼을 누른 할일의 id와 원래있던 할일목록들중에 id가 같은거를 지워야하니까 같은거빼고 다하면 되니까 같지않은거만 필터해줘(todo.id !== id) 하면 같은거빼고 필터됌(머리가 나빠서 이해하는데 한참걸렸음ㅋ)
다음은 토글(완료여부를 바꾸는건데 머리로 생각할때 완료여부가 바뀌면 working존과 done존을 왔다갔다 해야한다고 생각하니 머리가 아파졌음 그냥 완료여부만 바꿔주면 움직이는건데.. 왜냐면 isDone에 따라 필터를 해줬잖슴)

const onToggleTodoItem = (id) => {
    setTodos((prevTodos) =>
      prevTodos.map((todo) => {
        if (todo.id === id) {
          return {
            ...todo,
            isDone: !todo.isDone,
          };
        }
        return todo;
      })
    );
  };

이것도 id를 넘겨받아야한다 왜냐믄 내가 완료던지 취소던지를 누른 할 일만 완료여부를 바꿔줘야하기 때문이다
원래있던 할일들을 맵함수로 다시 그려줄거다.
내가 누른 할일의 id와 원래있던 id가 같다면(그니까 완료든 취소든 내가 상태변경을 위해 누른거임) 그 할일(내가선택한할일)을 spread해서 isDone을 반대로 바꿔줌! 만약 내가 선택한게 아니라면 그냥 원래 있던거 그대로 리턴함

짝짝짝 끝난듯^^..

profile
아자아자

0개의 댓글