7 분 소요

1️⃣ 버튼 및 공통 레이아웃 만들기

MyHeader.js

// src > components > MyHeader.js
const MyHeader = ({ headText, leftChild, rightChild }) => {
  return (
    <header>
      <div className="head_btn_left">{leftChild}</div>
      <div className="head_text">{headText}</div>
      <div className="head_btn_right">{rightChild}</div>
    </header>
  );
};

export default MyHeader;
/* src > App.css */
/* Header */
header {
  padding-top: 20px;
  padding-bottom: 20px;
  display: flex;
  align-items: center;
  border-bottom: 1px solid #e2e2e2;
}
header > div {
  display: flex;
}
header .head_text {
  width: 50%;
  font-size: 25px;
  justify-content: center;
}
header .head_btn_left {
  width: 25%;
  justify-content: start;
}
header .head_btn_right {
  width: 25%;
  justify-content: end;
}
header button {
  font-family: "Nanum Pen Script";
}

MyButton.js

// src > components > MyButton.js
const MyButton = ({ text, type, onClick }) => {
  const btnType = ["positive", "negative"].includes(type) ? type : "default";

  return (
    <button
      className={["MyButton", `MyButton_${type}`].join(" ")}
      onClick={onClick}
    >
      {text}
    </button>
  );
};
MyButton.defaultProps = {
  type: "default",
};
export default MyButton;

type에 따라서 css 디자인을 통해 다른 색깔이 나오도록 함.

/* src > App.css */
/* MyButton */
.MyButton {
  cursor: pointer;
  border: none;
  border-radius: 5px;

  padding-top: 10px;
  padding-bottom: 10px;
  padding-left: 20px;
  padding-right: 20px;

  font-size: 18px;
  white-space: nowrap;
  font-family: "Nanum Pen Script";
}
.MyButton_default {
  background: #ececec;
  color: black;
}
.MyButton_positive {
  background: #64c064;
  color: white;
}
.MyButton_negative {
  background: #fd565f;
  color: white;
}

2️⃣ 헤더 만들기

// src > pages > Home.js
import { useState } from "react";
// COMPONENTS
import MyHeader from "./../components/MyHeader";
import MyButton from "./../components/MyButton";

const Home = () => {
  const [curDate, setCurDate] = useState(new Date());

  const headText = `${curDate.getFullYear()}${curDate.getMonth() + 1}월`;
  // 버튼 클릭 -> 월이 바뀔 수 있도록 하는 메서드
  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} />}
      ></MyHeader>
    </div>
  );
};

export default Home;

3️⃣ 임시로 App 컴포에 dummyList 넣기

// src > App.js
const dummyData = [
  {
    id: 1,
    emotion: 1,
    content: "오늘의 일기 1번",
    date: 1658666575617,
  },
  {
    id: 2,
    emotion: 2,
    content: "오늘의 일기 2번",
    date: 1658666575619,
  },
  {
    id: 3,
    emotion: 3,
    content: "오늘의 일기 3번",
    date: 1658666575621,
  },
  {
    id: 4,
    emotion: 4,
    content: "오늘의 일기 4번",
    date: 1658666575623,
  },
  {
    id: 5,
    emotion: 5,
    content: "오늘의 일기 5번",
    date: 1658666575625,
  },
];

// data 기본값을 dummyData로 변경
const [data, dispatch] = useReducer(reducer, dummyData);

4️⃣ Home 컴포에서 dummyData 사용

일기가 작성된 날짜에 따라서 해당 월에 작성된 일기만 출력해야 되므로 Date 객체를 사용해서 일기가 작성될 날짜를 구한다.

const Home = () => {
  const diaryList = useContext(DiaryStateContext);

  // 현재 월에 해당하는 일기만 필요하므로 useState 사용
  const [data, setData] = useState([]);
  const [curDate, setCurDate] = useState(new Date());

  // 해당 달에 작성된 일기만 추리기 위한 코드
  useEffect(() => {
    if (diaryList.length >= 1) {
      const firstDay = new Date(
        curDate.getFullYear(),
        curDate.getMonth(),
        1
      ).getTime();
      const lastDay = new Date(
        curDate.getFullYear(),
        curDate.getMonth() + 1,
        0
      ).getTime();

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

  ...
export default Home;

일기 컴포넌트 만들기

components 폴더에 DiaryList.js 라는 컴포넌트를 만든다.
DiaryList 컴포넌트는 더미 리스트의 데이터를 하나의 리스트처럼 묶어서 사용할 수 있도록 만든 컴포넌트 이다.

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

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

export default DiaryList;

컴포넌트를 만들었으니까 Home 컴포넌트에 연결을 해주고, 추가해준다.

return (
  <div>
    <MyHeader
      headText={headText}
      leftChild={<MyButton text={"<"} onClick={decreaseMonth} />}
      rightChild={<MyButton text={">"} onClick={increaseMonth} />}
    ></MyHeader>
    <DiaryList diaryList={data}></DiaryList>
  </div>
);

DiaryList 정렬

헤더 밑에 있는 버튼 같은거! 최근순, 오래된 순, 좋은 감정, 안좋은 감정을 필터링 할 수 있는 기능을 만들 것이다.

  • 최신순, 오래된 순 option 만들기

    // src > components > DiaryList.js
    import { useState } from "react";
    
    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>
      );
    };
    
    const sortOptionList = [
      { value: "latest", name: "최신순" },
      { value: "oldest", name: "오래된 순" },
    ];
    const DiaryList = ({ diaryList }) => {
      const [sortType, setSortType] = useState("latest");
      return (
        <div>
          <ControlMenu
            value={sortType}
            onChange={setSortType}
            optionList={sortOptionList}
          ></ControlMenu>
          {diaryList.map((it) => (
            <div key={it.id}>{it.content}</div>
          ))}
        </div>
      );
    };
    
    DiaryList.defaultProps = {
      dirayList: [],
    };
    
    export default DiaryList;
    
    • value Prop: 어떤 종류의 select를 골랐는지
    • onChange Prop: select가 정의한 값이 바뀌었을 때 기능하는 함수
    • option Prop: select 태그 안에 들어있는 리스트

    ❓ key={idx}가 필요한 이유?

    선택을 했으면 알아서 sorting이 되어야하기 때문에 DiaryList 컴포넌트에 다음과 같은 메소드를 만들어 준다.

    const getProcessedDiaryList = () => {
      const copyList = JSON.parse(JSON.stringify(diaryList));
    };
    

    이렇게 된다면 diaryList -> stringify(문자열로 만들어줌) -> parse(다시 배열로 복구) -> copyList로 전달

    이렇게 해주었다면 date 비교를 통해서 sort 를 해준다.

    const getProcessedDiaryList = () => {
      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 sortedList = copyList.sort(compare);
      return sortedList;
    };
    

    그리고 getProcessedDiaryList 함수를 통해서 리스트가 출력되도록 하면 적용된 것을 확인할 수 있다.

    return (
      <div>
        <ControlMenu
          value={sortType}
          onChange={setSortType}
          optionList={sortOptionList}
        ></ControlMenu>
        {getProcessedDiaryList().map((it) => (
          <div key={it.id}>{it.content}</div>
        ))}
      </div>
    );
    
  • 좋은 감정, 안좋은 감정 sort

    const filterOptionList = [
      { value: "all", name: "모두다" },
      { value: "good", name: "좋은 감정만" },
      { value: "bad", name: "안좋은 감정만" },
    ];
    const DiaryList = ({ diaryList }) => {
      const [sortType, setSortType] = useState("latest");
      const [filter, setfilter] = useState("all");
    
      ...
    
      return (
        <div>
          <ControlMenu
            value={sortType}
            onChange={setSortType}
            optionList={sortOptionList}
          ></ControlMenu>
          // 추가
          <ControlMenu
            value={filter}
            onChange={setfilter}
            optionList={filterOptionList}
          ></ControlMenu>
          {getProcessedDiaryList().map((it) => (
            <div key={it.id}>
              {it.content} {it.emotion}
            </div>
          ))}
        </div>
      );
    

    좋은 감정, 안 좋은 감정만 sort 하는 방법은 반복문을 돌면서 true로 리턴되는 애들만으로 필터해주면 된다.

    const getFilterCallBack = (item) => {
      if (filter === "good") {
        return parseInt(item.emotion) <= 3;
      } else {
        return parseInt(item.emotion) > 3;
      }
    };
    
    const getProcessedDiaryList = () => {
      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) => getFilterCallBack(it));
      const sortedList = filteredList.sort(compare);
      return sortedList;
    };
    
  • 새일기 쓰기 버튼 만들기

    기존에 만들어 두었던 MyButton 컴포넌트를 이용하고, 초록색으로 나오게 하고 싶으니까 type={“positive”}, text={“새 일기 쓰기”}를 해준다. 그리고 onClick을 했을 때 new 페이지로 이동을 해야되니까 전에 했던 useNavigate 함수를 이용한다.

    return (
      <div className="DiaryList">
        <div className="menu_wrapper">
          <div className="left_col">
            <ControlMenu
              value={sortType}
              onChange={setSortType}
              optionList={sortOptionList}
            ></ControlMenu>
            <ControlMenu
              value={filter}
              onChange={setfilter}
              optionList={filterOptionList}
            ></ControlMenu>
          </div>
          <div className="right_col">
            // 추가
            <MyButton
              type={"positive"}
              text={"새 일기 쓰기"}
              onClick={() => navigate("./new")}
            ></MyButton>
          </div>
        </div>
    
        {getProcessedDiaryList().map((it) => (
          <div key={it.id}>
            {it.content} {it.emotion}
          </div>
        ))}
      </div>
    );
    

    css 적용을 위해서 className 지정

    /* DiaryList */
    .DiaryList .menu_wrapper {
      margin-top: 30px;
      margin-bottom: 30px;
    
      display: flex;
      justify-content: space-between;
    }
    .DiaryList .menu_wrapper .right_col {
      flex-grow: 1;
    }
    .DiaryList .menu_wrapper .right_col button {
      width: 100%;
    }
    .DiaryList .ControlMenu {
      margin-right: 10px;
      border: none;
      border-radius: 5px;
      background-color: #ececec;
    
      padding-top: 10px;
      padding-bottom: 10px;
      padding-left: 20px;
      padding-right: 20px;
    
      cursor: pointer;
      font-family: "Nanum Pen Script";
      font-size: 18px;
    }
    

DiaryList -> DiaryItem

// src > components > DiaryItem.js
import MyButton from "./MyButton";

const DiaryItem = ({ id, emotion, content, date }) => {
  const strDate = new Date(parseInt(date)).toLocaleDateString();
  return (
    <div className="DiaryItem">
      <div
        className={[
          "emotion_img_wrapper",
          `emotion_img_wrapper_${emotion}`,
        ].join(" ")}
      >
        <img
          src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`}
          alt=""
        />
      </div>
      <div className="info_wrapper">
        <div className="diary_date">{strDate}</div>
        <div className="diary_content_preview">{content.slice(0, 25)}</div>
      </div>
      <div className="btn_wrapper">
        <MyButton text={"수정하기"}></MyButton>
      </div>
    </div>
  );
};

export default DiaryItem;

코드 형태만 보면 그렇게 어렵지 않다. css가 많이 차지!

/* App.css */
/* DiaryItem */
.DiaryItem {
  padding-top: 15px;
  padding-bottom: 15px;

  border-bottom: 1px solid #e2e2e2;

  display: flex;
  justify-content: space-between;
}
.DiaryItem .emotion_img_wrapper {
  cursor: pointer;
  min-width: 120px;
  height: 80px;
  border-radius: 5px;
  display: flex;
  justify-content: center;
}
.DiaryItem .emotion_img_wrapper_1 {
  background-color: #64c964;
}
.DiaryItem .emotion_img_wrapper_2 {
  background-color: #9dd772;
}
.DiaryItem .emotion_img_wrapper_3 {
  background-color: #fdce17;
}
.DiaryItem .emotion_img_wrapper_4 {
  background-color: #fd8446;
}
.DiaryItem .emotion_img_wrapper_5 {
  background-color: #fd585f;
}

.DiaryItem .emotion_img_wrapper img {
  width: 50%;
}
.DiaryItem .info_wrapper {
  flex-grow: 1;
  cursor: pointer;
  margin-left: 20px;
}
.DiaryItem .diary_date {
  font-weight: bold;
  font-size: 25px;
  margin-bottom: 5px;
}
.DiaryItem .diary_content_preview {
  font-size: 10px;
}
.DiaryItem .btn_wrapper {
  min-width: 70px;
}

그리고 버튼 기능 구현 => 당연히 다른 페이지로 이동을 해야되니까 useNavigate 훅을 사용한다.

import { useNavigate } from "react-router-dom";
import MyButton from "./MyButton";

const DiaryItem = ({ id, emotion, content, date }) => {
  const navigate = useNavigate();
  const strDate = new Date(parseInt(date)).toLocaleDateString();

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

  const goEdit = () => {
    navigate(`/diary/${id}`);
  };

  return (
    <div className="DiaryItem">
      <div
        onClick={goDetail}
        className={[
          "emotion_img_wrapper",
          `emotion_img_wrapper_${emotion}`,
        ].join(" ")}
      >
        <img
          src={process.env.PUBLIC_URL + `assets/emotion${emotion}.png`}
          alt=""
        />
      </div>
      <div className="info_wrapper">
        <div className="diary_date">{strDate}</div>
        <div className="diary_content_preview">{content.slice(0, 25)}</div>
      </div>
      <div onClick={goEdit} className="btn_wrapper">
        <MyButton text={"수정하기"}></MyButton>
      </div>
    </div>
  );
};

export default DiaryItem;

태그:

카테고리:

업데이트:

댓글남기기