[달력 만들기 toy project] 날짜별 메모 CRUD ① : 날짜별로 메모 추가

이민선(Jasmine)·2023년 5월 13일
0
post-thumbnail

드디어 진짜가 왔다.. 이제 제일 하고 싶었던 날짜별 CRUD 구현!!

나의 짧디 짧은 리액트 인생에서 시도해 본 가장 복잡한 구조의 코드였다.

일단 prop의 흐름을 먼저 그림으로 보이자면,

미쎤 : ToDoCards에서 현재 클릭한 날짜에 해당하는 메모만 필터링해서 보여줘야 한다. 그러려면 현재 클릭한 날짜가 필요해!!!

App에 useState로 선언한 [currentDate, setCurrentDate]가 있고, 날짜를 클릭했을 때 변경된 새로운 날짜를 ToDoCards에 넘겨주려면 onClick을 연결하기 위해 setCurrentDate를 Dates 컴포넌트까지 넘겨주어야 했다.

후아후아 이렇게 복잡한 건 다른 사람이 짠 코드에서나 봤지 내가 직접 짜보는 건 처음이다! ㅋㅋㅋ

이 설계대로 컴포넌트를 하나씩 보자면,

App.tsx

import React, { useState } from "react";
import styled from "styled-components";
import Calendar from "./components/Calendar/Table";
import ToDoCards from "./components/ToDos/ToDoCards";

// styled component 생략

function App() {
// currentDate의 초기 상태가 오늘 날짜이므로 today 선언
  const today = new Date();
  const todaysYear = today.getFullYear();
  const todaysMonth = today.getMonth();
  const todaysDate = today.getDate();

// 현재 클릭한 날짜를 담을 currentDate 변수와 prop으로 넘길 setCurrentDate 선언
  const [currentDate, setCurrentDate] = useState<CurrentDate>({
    year: todaysYear,
    month: todaysMonth,
    date: todaysDate,
  });

  return (
    <Main>
    // Calendar에 setCurrentDate 넘겨주자.
      <Calendar setCurrentDate={setCurrentDate} />
    // 저 아래 Dates 컴포넌트에서 바꾼 currentDate을 ToDoCards로 넘겨주자.
      <ToDoCards currentDate={currentDate} />
    </Main>
  );
}

export default App;

currentDate 변수와 setCurrentDate 함수가 아주 중요한 역할을 한다!! 위에서 얘기했듯이 Dates 컴포넌트까지 도착하기 위해 Calendar 컴포넌트에 prop으로 넘겨주었다.

이제 Calendar 컴포넌트로 가보자!

Calendar.tsx

// import 생략
// styled-components 생략

interface ICalendarProp {
  setCurrentDate: (newDate: {
    year: number;
    month: number;
    date: number;
  }) => void;
}
// setCurrentDate을 prop으로 받아와서 Dates 컴포넌트에 넘겨줄 것.
function Calendar({ setCurrentDate }: ICalendarProp) {
  const dispatch = useDispatch();
  const state = useSelector((store: RootState) => store.calendar);

  const handleClickPrevYear = () => {
    dispatch(decreaseYear());
  };

  const handleClickNextYear = () => {
    dispatch(increaseYear());
  };

  const handleClickPrevMonth = () => {
    dispatch(decreaseMonth());
  };

  const handleClickNextMonth = () => {
    dispatch(increaseMonth());
  };

  const monthArray = [
  // month 원소 생략
  ];
  
  return (
    <Table>
      <YearWrapper>
        <Btn onClick={handleClickPrevYear}>◀️</Btn>
        <Year>{state.year}</Year>
        <Btn onClick={handleClickNextYear}>▶️</Btn>
      </YearWrapper>
      <MonthWrapper>
        <Btn onClick={handleClickPrevMonth}>◀️</Btn>
        <Month>{monthArray[state.month]}</Month>
        <Btn onClick={handleClickNextMonth}>▶️</Btn>
      </MonthWrapper>
      <Days />
      // setCurrentDate에 넘겨줌.
      <Dates
        year={state.year}
        month={state.month}
        setCurrentDate={setCurrentDate}
      />
    </Table>
  );
}
export default Calendar;

Calendar에서는 연도, 월 상태의 action을 관리하는 컴포넌트이다. 여기에서는 setCurrentDate를 사용하지 않는다. 오로지 Dates 컴포넌트에 전달하기 위해 받아온 것이므로, 이것도 나름 props-drilling이라면 틀린 말은 아닌듯?!

바로 Dates 컴포넌트로 가보자!!

Dates.tsx

interface DatesProps {
  year: number;
  month: number;
  setCurrentDate: (newDate: {
    year: number;
    month: number;
    date: number;
  }) => void;
}

function Dates({ year, month, setCurrentDate }: DatesProps) {

// 달력 알고리즘 생략

// 연도, 월, 일을 매개변수로 지정한 onClick 함수 선언
  const onClick = (dateInfo: { year: number; month: number; date: number }) => {
    setCurrentDate(dateInfo);
  };
  
// 달력의 날짜를 map을 이용하여 순회할 showDates 변수에 담긴 EachDate 컴포넌트에 onClick 연결
  const now = Date.now();
  const today = { year, month, date: date.getDate() };
  const showDates = daysOfThisMonth.map((week, i) => (
    <tr key={now + i}>
      {week.map((date, j) => (
        <EachDate
          key={now + j}
          today={today}
          row={{
            week: i,
            lastWeek: daysOfThisMonth.findIndex(
              (week, index) => index !== 0 && week.includes(1)
            ),
          }}
          day={j}
      // 클릭한 날짜의 year, month, date를 인자로 전달
          onClick={() => onClick({ year, month, date })}
        >
          {date ? date : null}
        </EachDate>
      ))}
    </tr>

드디어 클릭한 날짜의 연도, 월, 일을 onClick을 이용하여 setCurrentDate함수에 인자로 전달한다!!
클릭하게 되면 currentDate의 상태가 바뀌기 때문에 App.tsx에서도 변경된 상태에 접근이 가능해진다. 이것이 바로 lifting state up!!

와 나 상태 끌어올리기 했어!! 되게 어려워보였는데 나도 이런거 해봤다~~ ㅋㅋㅋ
이제 App.tsx에서 변경된 currentDate에 접근 가능하다. console로 출력해보자.

아 왜이렇게 세상 신기하지 ㅋㅋㅋㅋㅋ 이제 출력이 되니까 뭐든 할 수 있을 것 같은 자신감??!!!
은 아직 할게 많이 남았다. 이제 state을 ToDoCards 컴포넌트에 넘겨줘야 한다.

App.tsx return 부분

  return (
    <Main>
      <Calendar setCurrentDate={setCurrentDate} />
      ToDoCards에 방금 클릭한 currentDate을 넘겨주자~~
      <ToDoCards currentDate={currentDate} />
    </Main>
  );

ToDoCards.tsx

// import 생략
// styled-components 생략

interface ICurrentDate {
  currentDate: CurrentDate;
}

// currentDate prop으로 받아왔으~~
function ToDoCards({ currentDate }: ICurrentDate) {
  const dispatch = useDispatch();
  const [memo, setMemo] = useState("");
  const toDosList = useSelector((state: RootState) => state.toDos);

  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setMemo(event.target.value);
  };

// 메모를 추가하는 함수 정의. dispatch의 인자로 addToDo action을 전달. 이 때 인자로 currentDate과 id, text를 넘겨준다.
  const handleAddMemo = () => {
    if (!memo) return;
    dispatch(addToDo({ ...currentDate, id: Date.now(), text: memo }));
    setMemo("");
  };
  
  // ⭐️ currentDate의 마지막 도착지!!! 오늘 날짜에 해당하는 메모만 filter한 배열인 currentList 선언.
    const currentList = toDosList.filter(
    (memo: ToDo) =>
      memo.year === currentDate.year &&
      memo.month === currentDate.month &&
      memo.date === currentDate.date
  );
  
    return (
    <CardsWrapper>
    // 이건 오늘 날짜를 화면에 한눈에 볼 수 있게 만든 h1 태그
      <h1>{`${currentDate.year}${currentDate.month + 1}${currentDate.date}`}</h1>
      <Input value={memo} onChange={onChange}></Input>
      <AddBtn onClick={handleAddMemo}>추가</AddBtn>
      <Cards>
      // 오늘 날짜의 메모만 있는 배열을 map을 이용하여 뿌려주자~~~
        {currentList.map((memo: ToDo) => (
          <Card key={memo.id}>
            {memo.text}
            // 메모 카드에도 선택한 날짜가 적혀 있다.
            <p>{`${memo.year}${memo.month + 1}${memo.date}`}</p>
            {/* <Btn onClick={() => handleUpdate(memo.id)}>수정</Btn>
            <Btn onClick={() => handleDelete(memo.id)}>삭제</Btn> */}
          </Card>
        ))}
      </Cards>
    </CardsWrapper>
  );
}
export default ToDoCards;

와우... 나 운다.. (뻥임 여튼 감동의 도가니임) 감동 받아서 중간에 날짜 광클함 ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ

  • 현재 클릭한 날짜가 input 위에 대문짝 만하게 뜬다.
  • 클릭한 날짜에 해당하는 메모만 filter되어 뜬다.

여기까지가 state 끌어올려서 클릭한 날짜에 해당하는 메모 필터링, 추가 기능 구현!!!!

와웅~~~ 정말 기나긴 여정이었다 ㅋㅋㅋㅋㅋㅋ

(조금 긴) 짧은 회고

머릿속에 생각만 해봤던 날짜 클릭하면 해당 날짜의 메모 보여주기를 실제로 구현해볼 수 있다니 너무 뿌듯하다!!! 물론 쉽지 않은 여정이었다. 컴포넌트 계층 구조 파악해서 상태 흐름을 lifting state up과 prop으로 만드는 일련의 과정을 혼자서 해보는게 처음이라 쉽지 않은 여정이었지만, 자신감이 많이 생겼다.

물론 아쉬운 점은 있다. 2가지가 있는데, 이번에는 포스팅에는 유독 마음이 급해서 에러 로그를 자세히 못담았다. 타입 에러부터 시작해서 정말 많이 마주쳤는데 기록이 없는 점이 아쉽다. 다음부터는 에러를 만나면 포스팅에 많이 담고 또 에러를 충분히 공부하자. 에러 공부는 소중하다. 다음에 똑같은 에러를 또 만났을 때 처치할 방법을 익히는 거니까 의미있는 시간 투자이다.

또 아쉬운 점 하나 더! 내가 이번 포스팅에서 구현한 상태 관리를 차라리 redux로 했으면 더 좋았을 뻔 했다. CRUD를 시작할 시점에는 오늘 날짜의 메모 보여주기를 redux로 구현할 생각을 못했었다.

반면 연도/월은 redux 연습하려고 useState 쓰다가 redux로 갈아탄거지만, 오히려 연도/월은 상태 관리가 간단해서 redux보다 useState가 더 편할 것이다. 그래서 useState로 다시 바꾸기로 결정했다.
상태마다 효율적인 관리 방법이 있다는 것도 중요한 배움이었다. 상태 흐름이 복잡할 수록 단방향 데이터 흐름인 redux의 사용 가치가 높아진다는 것!

profile
기록에 진심인 개발자 🌿

0개의 댓글