스터디 관리 프로젝트 - TODOLIST

Seuling·2023년 3월 7일
0

이프로젝트는, 일단 todolist의 확장판이라 할 수 있다.
기본적으로 기능을 붙여야한다 생각하였을때, todolist의 crud 가 선행되어야하고, 이후 다른 데이터바인딩이 필요하며, 그 이후 세부적인 다른 멤버를 클릭시 다른 멤버의 데이터가 보여야한다던지, 스케쥴러 작업과 같은 일을 해야한다 생각하였다.

그 중 먼저 todolist 를 보면, 1. TODO 하단의 + ADDTODO 로 Create 하기, 2. create 후 위에서 Read, 3. 연필아이콘 클릭시 Edit으로 Update, 4. 휴지통아이콘 클릭시 Delete, 5. 체크버튼 클릭시 todo update

TODO - Create

//Todo.tsx
export default function Todo() {
  const [isAdd, setIsAdd] = useState(false);
  const newTodo = {
    todoContent: todoContent,
    author: 24,
  };
  
  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    setTodoContent(e.target.value);
  };
  const onSubmit = async (e: FormEvent<HTMLFormElement>) => {
    e.preventDefault();
    await createTodo(newTodo);
    setTodoContent("");
    setIsAdd(false);
    fetchData();
  };

  return (
~~~
       {isAdd ? (
          <form onSubmit={onSubmit} className="add-todo-container">
            <input
              type="text"
              placeholder="할 일을 입력 후, Enter 를 누르세요"
              onChange={onChange}
              autoFocus={true}
            />
          </form>
        ) : (
          <label
            className="add-todo-container"
            onClick={() => {
              setIsAdd(true);
            }}
          >
            <Button variant="text">
              <Icon.Plus size={20} color="#635fc7" />
            </Button>
            <Text
              type="title"
              size="lg"
              color="primary"
              style={{ paddingLeft: "10px" }}
            >
              ADD TODO
            </Text>
          </label>
        )}
    )
}
  • 먼저, isAdd 라는 상태를 만들어놓고, 새로운 newTodo 라는 객체를 만들어준다.

  • 여기서 author : 24인 이유는 strapi특성상 author에 해당하는 ID값이 들어가야하는데, 로그인한 유저의 ID값을 받아와야하지만, 아직 로그인 로직이 처리되지 않아서 임의의 user인 ID값 24를 넣어주었다. (나중에 로그인 구현 후 수정 예정)

  • onChange 함수는 말그대로 change될때의 e.target.value의 값을 set해주는 것!

  • onSubmit 함수는 submit 시 form의 기본동작인 새로고침을 막아주는 e.preventDefault()를 실행시키고, createTodo(newTodo)를 호출 한 뒤, setTodoContent의 값은 빈값으로 비어주고, 이제 작성중이 아니니까 setIsAdd(false), 그 이후 fetchData()를 한번 더 해주는 이유는? 새로고침을 해야 리렌더링이 되는 이슈가 있었는데, 이부분을 submit시 마지막에 fetchData를 해줌으로써, 전송과 즉시 데이터를 다시 패칭해주는 역할을 하게된다.

  • return 이하를 보자면, 조건부 렌더링으로 isAdd의 상태에 따라 컴포넌트를 다르게 보여준다.

  • isAdd 가 true인 경우는 새로운걸 작성할 상태이니까, input 태그를 보여주고,

  • isAdd가 false인 경우는 + ADD TODO의 버튼을 보여준다.

TODO - Read

export default function Todo() {
  const { data: todo, fetchData } = useAPI<TodoEntity>(fetchTodo, {
    isFetch: true,
  });
  
  return (
  <div className="todo-tontent-layout-wrapper">
          {todo.map((t) => {
            return (
              <TodoContent
                key={`${t.publishedAt}+1`}
                content={t.todoContent!}
                id={t.id!}
                fetchData={fetchData}
              />
            );
          })}
        </div>
  )
}
  • 먼저 useAPI로 todo에 해당하는 data를 가져온다.
  • todo의 데이터가 배열 이기에, map을 돌려줄것인데, 각각의 todo를 <TodoContent/> 라는 컴포넌트로 return 해 줄 것이다.
    - forEach 와 map의 차이는 ? return 여부, return 을 해주는것은 map, forEach는 return없이 순회만 하는것!(keyPoint!! 배열을 리턴하지않음) 이 차이를 알고도 map을 하고 return 을 사용하지 않아서 화면에 렌더링되지않아 계속 해맸던 시간을 가졌다... 😭

TODO - Edit, Delete

<TodoContent/>

interface Props {
  content: string;
  id: number;
  fetchData: () => Promise<void>;
}

export default function TodoContent({ content, id, fetchData }: Props) {
  const formRef = useRef(null);
  const [isEdit, setIsEdit] = useState(false);
  const [editTodoContent, setEditTodoContent] = useState(content);
  const [isOpenModal, setIsOpenModal] = useState(false);
  const clickModal = () => {
    setIsOpenModal(true);
  };
  const closeModal = () => {
    setIsOpenModal(false);
  };
  const editTodo: TodoParam = {
    todoContent: editTodoContent,
    author: 24, //TODO: 로그인한 사용자의 id 값을 넣어줘야함
  };

  const onChange = (e: ChangeEvent<HTMLInputElement>) => {
    setEditTodoContent(e.target.value);
  };
  const onSubmit = async (
    e: React.FormEvent<HTMLFormElement>,
    todo: TodoParam
  ) => {
    e.preventDefault();
    await updateTodo(todo, id);
    if (todo === editTodo) {
      setIsEdit(false);
      fetchData();
    } else {
      setIsOpenModal(false);
    }
  };

  const editFunc = () => {
    setIsEdit(!isEdit);
    if (isEdit && formRef) {
      (formRef.current as any).dispatchEvent(
        new Event("submit", { cancelable: true, bubbles: true })
      );
    }
  };

  return (
    <>
      <div className="todo-content-container">
        <div className="todo-content-wrapper">
          <button
            onClick={() => {
              clickModal();
            }}
          >
            <Icon.CheckCircle size={18} color="white" />
          </button>

          {isEdit ? (
            <form
              ref={formRef}
              onSubmit={(e: React.FormEvent<HTMLFormElement>) =>
                onSubmit(e, editTodo)
              }
            >
              <input type="text" defaultValue={content} onChange={onChange} />
            </form>
          ) : (
            <Text style={{ paddingLeft: "10px" }}>{content}</Text>
          )}
        </div>

        <div className="todo-icon-wrapper">
          <button onClick={() => editFunc()}>
            <Icon.Edit2 size={18} color="white" />
          </button>
          <button
            onClick={async () => {
              await deleteTodo(id as number);
              fetchData();
            }}
          >
            <Icon.Trash size={18} color="white" />
          </button>
        </div>
      </div>
      {isOpenModal && (
        <Modal
          variant="certification"
          onSubmit={onSubmit}
          closeModal={closeModal}
        />
      )}
    </>
  );
}

  • 먼저, 편집중인지 isEdit의 상태를 만들어주었고, 편집된 내용의 editTodoContent의 상태또한 만들었다.
  • editTodo, onChange 의 경우 위의 create부분과 동일하게 구현
  • onSubmit 함수 에서 form의 기본동작인 새로고침을 막아주는 e.preventDefault()를 실행하고, updateTodo()를 호출하는데, updateTodo의 보내질 todoeditTodo 내용이 같으면, setIsEdit(false)로 해주고, 아닐 경우는 왜 setIsOpenModal(false)를 해줬지...??? 😓 -> setIsOpenModal(false)를 안해주고 그냥 없애보니까, edit은 잘 동작하나, 만약 체크 버튼을 클릭하고 확인을 누를경우 onSubmit은 동작하지만 모달창이 꺼지지않는 문제가 있음...! 그래서 해주었구나!!
  • editFunc을 자세히 보자! 먼저 편집중인 상태이니까, isEdit의 상태를 변경해주고, isEdit && formRef 일 경우, formRef의 값을 가져와서 사용하면되는데, 적용이안되어 구글링해보니, 리액트17 부터 이벤트에 cancelable과 bubbles properties 에 대한 true false 값을 적어줘야한다고 나와있다.
  • delete의 경우 삭제 아이콘에 onClick에 deleteTodo(), fetchData()를 해주면 되는데, 이 두개의 결과가 비동기적으로 일어남으로 delete를 해준 뒤 fetch해와야함으로 async await을 해주었다.
profile
프론트엔드 개발자 항상 뭘 하고있는 슬링

0개의 댓글