감정일기장 만들기 2 - CRUD 기능 구현 (1) HOME

동화·2023년 3월 7일
0

diary-project

목록 보기
2/8
post-thumbnail

HOME

홈화면에서는 헤더에 날짜, 그리고 < > 버튼을 누르면 이전 달로 이동하도록 만들고,
또 일기 리스트는 필터도 적용한다.

Home.js

const headText = `${curDate.getFullYear()}${curDate.getMonth() + 1}`;

일단 헤더의 가운데 글씨 부분을 설정
근데 자바스크립트의 getMonth()함수는 이상해서 그냥 실행하면 첫달이 1이 아니고 0이 된다고 한당
그래서 +1 을 해주어야 함.

그리고 이제 버튼 양 옆을 누르면 이전 달로 이동해야함.
그래서 현재 헤더에 표시된 날짜를 저장하는 state를 생성한다.

 //헤더 날짜 저장하는 state
  const [curDate, setCurDate] = useState(new Date());

그러고 나서 월 증가/감소 함수를 만들어 줌.

const increaseMonth = () => {
    setCurDate(
      new Date(curDate.getFullYear(), curDate.getMonth() + 1, curDate.getDate())
    );
  };

  const decreaseMonth = () => {
    setCurDate(
      new Date(curDate.getFullYear(), curDate.getMonth() - 1, curDate.getDate())
    );
  };

...

return (
  <div>
      <MyHeader
        headtext={headText}
        leftchild={
          <MyButton
            text={"<"}
            onClick={() => {
              decreaseMonth();
            }}
          />
        }
        rightchild={
          <MyButton
            text={">"}
            onClick={() => {
              increaseMonth();
            }}
          />
        }
      />
    </div>
  );
...

List

일단 필터는 두 종류인데,

  • 최신순, 오래된 순 (정렬)
  • 기분 좋은 날/ 안 좋은 날만 보기 (필터)
    이 필터를 구현하려면 일기 리스트가 있어야 하는데 아직 작성부분은 구현하지 않았으므로
    더미데이터를 끌고 와본다.

App.js

const dummyData = [
  {
    id: 1,
    emotion: 1,
    content: "오늘의 일기1",
    date: 1678172343286,
  },
  {
    id: 2,
    emotion: 2,
    content: "오늘의 일기2",
    date: 1678172343287,
  },
  {
    id: 3,
    emotion: 3,
    content: "오늘의 일기3",
    date: 1678172343288,
  },
  {
    id: 4,
    emotion: 4,
    content: "오늘의 일기4",
    date: 1678172343289,
  },
  {
    id: 5,
    emotion: 5,
    content: "오늘의 일기5",
    date: 1678172343290,
  },
];

date 값은 ms값으로 넣어야 하기 때문에,
newDate().getTime() 을 콘솔에 찍어서 확인한다.
console.log(new Date().getTime())

하나만 넣어주고 나머지는 숫자 1씩 올렸다. 그래야 정렬을 해볼 수 있으니깐!

그리고 원래 useReducer(reducer, []) 였던 부분에
dummyData를 기초값으로 넣어준당

const [data, dispatch] = useReducer(reducer, dummyData);

Home.js

const diaryList = useContext(DiaryStateContext);

이제 근데 다이어리 게시글이 월이 바뀌면 보이지 않아야 한다.
나는 지금 더미데이터를 2023.3월 기준으로 만들었으므로, 2월 4월에는 보이지 않게 해야함..
즉 월 별로 다이어리리스트를 맞는 날짜에 보이게 하기 위한 작업을 해야 한다.

const [data, setData] = useState([]);

useEffect를 이용해서 만들어 줌.
여담인데 date랑 data랑 헷갈려서 미치게따 ㅎ_ㅎㅋㅋㅋㅋㅋ

useEffect(() => {
    if (diaryList.length >= 1) {
      // 그냥 진행되면 매우 오래 걸리는 작업이므로 list가 없을 땐 실행 x
      const firstDay = new Date(
        curDate.getFullYear(),
        curDate.getMonth(),
        1
      ).getTime();
      // 매달의 첫 번째 날을 ms로 구해 줌

      const lastDay = new Date(
        curDate.getFullYear(),
        curDate.getMonth() + 1,
        0
      ).getTime();
      // 매달의 마지막 날을 구해줌

      setData(
        diaryList.filter((it) => firstDay <= it.date && it.date <= lastDay)
      );
    }
  }, [curDate]);

여기까지 하면 오류가 생긴다. diaryList 빼먹은거아냐~? 하고 알려줌
만약에 [curDate]만 있다면, diaryList가 변경되었을 땐(글 추가,수정, 삭제 등)실행이 되지가 않기 때문에 꼭 diaryList를 넣어주어야 한다.

...
}, [diaryList, curDate]);

이 데이터 5개 중 하나의 날짜를 바꾸어 보면,


(비교 쉬우라고 원래 시간은 주석처리하고 숫자 바꿈 -> 매우매우 미래가 되었다)
3월 리스트에서 일기 하나가 빠진 걸 볼 수 있다. 눌러보면 5번은 여기 없음.

이제 다이어리 목록을 component로 만들어주고 Home에 뿌려줌

components/DiaryList.js

const DiaryList = ({ diaryList }) => {
  return (
    <div>
      {diaryList.map((it) => (
        <div key={it.id}>{it.content}</div>
      ))}
    </div>
  );
};

export default DiaryList;

pages/Home.js

...
<DiaryList diaryList={data} />

그리고 추가로

DiaryList.defaultProps = {
  diaryList: [],
};

도 해주었는데 defaultProps 에 대한 자세한 정보는 여기에서... 하기로 함!



Filter

최신 순, 오래된 순

DiaryList.js

const sortOptionList = [
  { value: "latest", name: "최신 순" },
  { value: "oldest", name: "오래된 순" },
];

const ControlMenu = ({ value, onChange, optionList }) => {
  return (
    <select value={value} onChange={(e) => onChange(e.target.value)}>
      {optionList.map((it, idx) => (
        <option key={idx} value={it.value}>
          {it.name}
        </option>
      ))}
    </select>
  );
};

value : select에서 현재 어떠한 역할인지에 대한 정보
onChange : select서 선택하는게 변화하였을 때 바꿀 기능을 할 함수
optionList : <select> 안에 들어갈 옵션 (최신 순, 오래된 순)

여기서 <select value={value} 를 써주는 이유

  • state를 이용한 select 태그의 초기값 설정이 가능하기 때문.
  • 예를 들어 select 태그가 화면에 렌더링 되자 마자 특정 값이 선택된 상태를 구현하고자 하는 상황에 이용할 수 있다.
const DiaryList = ({ diaryList }) => {
  const [sortType, setSortType] = useState("latest");

  return (
    <div>
      <ControlMenu
        value={sortType}
        onChange={setSortType}
        optionList={sortOptionList}
      />
   ...
        

select 내에서 onChange 이벤트가 발생하게 되면, 이벤트 객채의 target.value를 전달을 해서
prop으로 받은 onChange 메서드를 실행시키는데, 그 onChange 메서드는 setSortType이었기 때문에
오래된 순을 선택하게 되면 oldest가 되게 되고, 최신 순을 선택하게 되면 latest가 된다.

이제 리스트가 바뀌는 걸 구현해보쟈

const getProcessDiaryList = () => {
    const compare = (a, b) => {
      if (sortType === "latest") {
        return parseInt(b.date) - parseInt(a.date);
        // 문자열이 들어올 수도 있기 때문에 parseInt를 해주어야 함 (숫자로 바꾸어 줌)
      } else {
        return parseInt(a.date) - parseInt(b.date);
      }
    };

    const copyList = JSON.parse(JSON.stringify(diaryList));
    const sortedList = copyList.sort(compare);
    return sortedList;
 };

sort()를 쓰면 원본배열이 변화하기 때문에 배열을 copy. 깊은 복사를 해준다(copyList)
JSON.stringify(diaryList) 는 diaryList를 문자화시킴
그걸 JSON.parse로 실행시키면 다시 배열로 됨 그래서 배열 자체 모양은 건드려지지 않은 채 깊은 복사가 됨.

혹시 스프레드 연산자로 가능할까?

  • 스프레드 연산자도 깊은 복사의 방법 중 하나이지만, 이는 1차원 배열 or 객체에만 해당되기 때문에 다차원의 데이터에서는 JSON을 이용해야 한다. (lodash라는 라이브러리도 있다고 함)

그리고 리스트 map으로 뿌려줬던 부분을

	{getProcessDiaryList().map((it) => (
        <div key={it.id}>{it.content}</div>
     ))}

로 바꾸어 준다.
함수니까 getProcessDiaryList()에서 () 꼭 빼먹지 말기~





감정 필터

  const [filter, setFilter] = useState("all");
const filterOptionList = [
  { value: "all", name: "전부 다" },
  { value: "good", name: "좋은 감정만" },
  { value: "bad", name: "나쁜 감정만" },
];

위에서 만들었던 컨트롤메뉴에 prop 받아서 넣어준다

	<ControlMenu
        value={filter}
        onChange={setFilter}
        optionList={filterOptionList}
     />

아 그리고 리스트에 감정이 있어야 구분이 쉬우니까 {it.emotion}도 추가해 줌ㅋㅋ

filterList 를 구현해 주는데,
만약에 filter가 all 일 경우, copyList를 반환하고, 아닐 경우에는 copyList를 필터한 것을 반환한다.
이때 필터는 또 함수를 하나 생성해서,
filter가 good이면 감정이 1,2 인 (임시) 글을, bad 이면 4,5인 글을 필터링하게 함.
그리고 위에서 만들었던 sortedList도 바꾸어 준다.
이것들을 실행한 코드

const getProcessDiaryList = () => {
    const filterCallback = (item) => {
      if (filter === "good") {
        return parseInt(item.emotion) <= 2;
      } else if (filter === "bad") {
        return parseInt(item.emotion) >= 4;
      }
    };

    const compare = (a, b) => {
      if (sortType === "latest") {
        return parseInt(b.date) - parseInt(a.date);
      } else {
        return parseInt(a.date) - parseInt(b.date);
      }
    };

    const copyList = JSON.parse(JSON.stringify(diaryList));
    const filteredList =
      filter === "all" ? copyList : copyList.filter((it) => filterCallback(it));

    const sortedList = filteredList.sort(compare);
    return sortedList;
  };

copyList.filter((it) => filterCallback(it))
이부분은 it이 filterCallback을 받았을 때 return true를 반환하게 하는 애들로 필터링 해주는 것이다.
만약 !filterCallback(it)이라고 적으면 나쁜 감정일 때 123, 좋은 감정일 때 345가 나옴.




새 글 작성 버튼

useNavigate를 이용한다.

const navigate = useNavigate();

  ...

return (
 	 <MyButton
        type={"positive"}
        text={"새로운 일기쓰기"}
        onClick={() => {
          navigate("/new");
        }}
      />
   ...	
)
    

이제 이 버튼을 누르면 새 글 작성하는 페이지로 이동함! 이건 매우매우 매우 간단!



List

DiaryList.js

      {getProcessDiaryList().map((it) => (
        <DiaryItem key={it.id} {...it} />
      ))}



일단 DiaryItem 컴포넌트에서 받아와야 할 prop는 { id, emotion, content, date } 이렇게 네 개 이다.

emotion 이미지

DiaryItem.js

<div className={["emotion_img", `emotion_img_${emotion}`].join(" ")}>
        <img src={process.env.PUBLIC_URL + `/assets/emotion${emotion}.png`} />
      </div>

emotion_img_${emotion} 이걸 붙여준 이유는 다섯 개의 이미지를 다 각자 선택해서 css를 수정하기 위해서이다.
사진하나를 console.log에 찍어보게 되면

이렇게 나오고, join(" ") 덕분에 중간의 쉼표도 나오지 않는다.
다른 사진들을 눌러보면 뒤의 숫자만 1, 2, 3, .. 하고 바뀜.
근데 나는 이 기능을 사실상 이용할 일이 없어서 알아만 두려고 적어 놓는다.


❗️ 이미지가 안 뜰 경우

  const env = process.env;
  env.PUBLIC_URL = env.PUBLIC_URL || "";

을 위에 넣어준다.

그리고 css 어느정도 손보고 넘어가자




날짜 & 일기 프리뷰 & 수정 버튼

const strDate = new Date(parseInt(date)).toLocaleDateString();

..

	<div className="diary_date">{strDate}</div>
    <div className="diary_content_prev">{content.slice(0, 25)}</div>
    <div className="btn_wrap">
        <MyButton text={"수정하기"} />
    </div>

프리뷰이기 때문에 n자까지만 잘라서 보여주게 만들었다.
그래서 더미데이터도 수정함




링크 연결

  const goDetail = () => {
    navigate("/diary/${id}");
  };

  const goEdit = () => {
    navigate("/edit/${id}");
  };

해서 각각 div에 onClick 이벤트로 삽입해준다.

홈까지 수정한 내용이다.
내일 prev에 ...을 뒤에 넣어보려고 한당 !!
오늘 기능 구현 다 넣으려했는데
이것저것 찾아보고 블로깅하느라고 시간이 꽤 오래 걸렸다 ㅠ.ㅠ 아쉬워

6개의 댓글

comment-user-thumbnail
2023년 3월 7일

오늘하루만에 많은걸 하셨는걸요 ? 구현방법 자세히 설명해줘서 알기 쉬웠습니다!

답글 달기
comment-user-thumbnail
2023년 3월 12일

올마나 열심히 하시는겁니까...

답글 달기
comment-user-thumbnail
2023년 3월 12일

끝도 없는 스크롤.. 기능 추가를 엄청 하시는 군요 결과가 기대됩니다

답글 달기
comment-user-thumbnail
2023년 3월 12일

우와 ,,, 엄청나요 ....

답글 달기
comment-user-thumbnail
2023년 3월 12일

와. 이걸 정리까지 하시면서 하셨다니 대단해요.

답글 달기
comment-user-thumbnail
2023년 3월 12일

우와... 엄청 친절한 설명에 감동했습니다ㅠㅠ 마지막 결과물도 너무 예뻐요 😍

답글 달기