React Emotion Diary - Home
<홈>
Home.js
// 1. 현재 date를 구현한다 2. 왼쪽 버튼 3. 오른쪽 버튼
import { useState } from "react";
import MyHeader from "./../components/MyHeader";
import MyButton from "./../components/MyButton";
const Home = () => {
const [curDate, setCurDate] = useState(new Date());
// getMonth를 하면 1월 = 0월 이라서 +1을 해야함
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} />}
/>
</div>
);
};
export default Home;
실행 화면
<내용 불러오기>
DiaryList.js
const DiaryList = ({ diaryList }) => {
return (
<div>
{/* props로 전달받은 diaryList를 map으로 리스트 렌더링 */}
{diaryList.map((it) => (
<div key={it.id}>{it.content}</div>
))}
</div>
);
};
// diaryList prop이 정상적으로 전달이 안될 수 있으므로 default props 사용
DiaryList.defaultProps = {
diaryList: [],
}
export default DiaryList;
Home.js
import { useContext, useEffect, useState } from "react";
import { DiaryStateContext } from "../App";
import MyHeader from "./../components/MyHeader";
import MyButton from "./../components/MyButton";
import DiaryList from "../components/DiaryList";
const Home = () => {
const diaryList = useContext(DiaryStateContext);
const [data, setData] = useState([]);
const [curDate, setCurDate] = useState(new Date());
const headText = `${curDate.getFullYear()}년 ${curDate.getMonth() + 1}월`;
//curDate가 변화하는 순간에만 useEffecr로 년도와 월에 해당하는 일기 데이터만 뽑아옴
useEffect(() => {
// diaryList가 비어있는 상황에서는 동작될 필요가 없으므로 1 이상일 경우에만 동작해라
if (diaryList.length >= 1) {
}
const firstDay = new Date(
curDate.getFullYear(),
curDate.getMonth(),
// 이번년도 이번월의 1일이 됨
1
).getTime();
const lastDay = new Date(
curDate.getFullYear(),
curDate.getMonth() + 1,
0
).getTime();
setData(
diaryList.filter((it) => firstDay <= it.date && it.date <= lastDay)
);
// diaryList가 바꼈다는건 일기가 새로 추가 됐거나, 수정 됐거나, 삭제 됐다는걸 의미 => List도 변경 해야 함
}, [diaryList, curDate]);
useEffect(() => {
console.log(data);
}, [data]);
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} />}
/>
<DiaryList diaryList={data} />
</div>
);
};
export default Home;
App.js
import React, { useReducer, useRef } from "react";
import "./App.css";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import New from "./pages/New";
import Edit from "./pages/Edit";
import Diary from "./pages/Diary";
const reducer = (state, action) => {
let newState = [];
switch (action.type) {
case "INIT": {
return action.data;
}
case "CREATE": {
newState = [action.data, ...state];
break;
}
case "REMOVE": {
newState = state.filter((it) => it.id !== action.targetId);
break;
}
case "EDIT": {
newState = state.map((it) =>
it.id === action.data.id
? {
...action.data,
}
: it
);
break;
}
default:
return state;
}
return newState;
};
export const DiaryStateContext = React.createContext();
export const DiaryDispatchContext = React.createContext();
const dummyData = [
{
id: 1,
emotion: 1,
content: "오늘의일기 1번",
// date의 값은 ms로 넣어야 함
date: 1648633885549,
},
{
id: 2,
emotion: 2,
content: "오늘의일기 2번",
// date의 값은 ms로 넣어야 함
date: 1648633885550,
},
{
id: 3,
emotion: 3,
content: "오늘의일기 2번",
// date의 값은 ms로 넣어야 함
date: 1648633885551,
},
{
id: 4,
emotion: 4,
content: "오늘의일기 4번",
// date의 값은 ms로 넣어야 함
date: 1648633885552,
},
{
// 가장 최신의 일기글
id: 5,
emotion: 5,
content: "오늘의일기 5번",
// date의 값은 ms로 넣어야 함
date: 1648633885553,
},
];
function App() {
// 확인을 위해서 []에 dummyData를 기초 값으로 넣어줌
const [data, dispatch] = useReducer(reducer, dummyData);
// 현재 date의 ms 구하는 방법
console.log(new Date().getTime());
const dataId = useRef(0);
const onCreate = (date, content, emotion) => {
dispatch({
type: "CREATE",
data: {
id: dataId.current,
date: new Date(date).getTime(),
content,
emotion,
},
});
dataId.current += 1;
};
const onRemove = (targetId) => {
dispatch({ type: "REMOVE", targetId });
};
const onEdit = (targetId, date, content, emotion) => {
dispatch({
type: "EDIT",
data: {
id: targetId,
date: new Date(date).getTime(),
content,
emotion,
},
});
};
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={(onCreate, onRemove, onEdit)}>
<BrowserRouter>
<div className="App">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/new" element={<New />} />
<Route path="/edit" element={<Edit />} />
<Route path="/diary/:id" element={<Diary />} />
</Routes>
</div>
</BrowserRouter>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
}
export default App;
실행 화면
<오래된순, 최신순 정렬>
DiaryList.js
import { useState } from "react";
const sortOptionList = [
{ value: "latest", name: "latest" },
{ value: "oldest", name: "oldest" },
];
// value = control 메뉴가 렌더링 하는 select가 어떤걸 선택 하고 있는지 역할
// onChange = select가 선택하는게 변화 했을때 바꿀 기능을 할 함수
// optionList = select 태그안에 들어갈 옵션
const ControlMenu = ({ value, onChange, optionList }) => {
return (
//onChange 이벤트가 일어나게 되면 이벤트 이벤트 객체에 target에 value를 전달해서 prop으로 받은 onChange 매서드를 실행 시키는데
// prop으로 준 onChange 메서드는 setSortType이였기 때문에 sortType을 oldest를 선택하면 oldest가 되고, latest를 선택하면 latest가 됨
<select value={value} onChange={(e) => onChange(e.target.value)}>
{/* it = sortOptionList에 latest,oldest 객체를 가르킴 */}
{optionList.map((it, idx) => (
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
};
const DiaryList = ({ diaryList }) => {
// 정렬 기준을 저장할 state
//sortType 정렬기준을 바꾸는 select의 역할
const [sortType, setSortType] = useState("latest");
// 최신순인지 오래된순인지 if문으로 분기를 달아서 정렬된 리스트를 반환하는 역할을 하는 함수
const getProcessDiaryList = () => {
// 정렬하고 하는 데이터가 배열이면 그냥 정렬이 안됨 => 비교 함수를 만들어 줘야함
const compare = (a, b) => {
if (sortType === "latest") {
// 문자열이 들어올 수 있기 때문에 parseInt
//date값을 비교해 가장 최신에 작성한게 앞
return parseInt(b.date) - parseInt(a.date);
} else {
return parseInt(a.date) - parseInt(b.date);
}
};
//sort 하면 원본 배열이 정렬 되므로 copy해 사용
// JSON.stringify(diaryList) => diaryList가 배열이어서 배열을 JSON화 시켜서 문자열로 바꿈
// 문자열로 반환된걸 JSON.parse를 수행 시키게 되면 다시 배열로 복호화
// diaryList에 있는 원본 배열의 값이 문자열로 바꼈다가 다시 배열로 바껴서 값만 들어옴 => DiaryList가 저장하고 있는 배열을 건드리지 않을 수 있음
const copyList = JSON.parse(JSON.stringify(diaryList));
const sortedList = copyList.sort(compare);
return sortedList;
};
return (
<div>
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
{getProcessDiaryList().map((it) => (
<div key={it.id}>{it.content}</div>
))}
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
실행 화면
<감정 필터>
DiaryList.js
import { useState } from "react";
const sortOptionList = [
{ value: "latest", name: "latest" },
{ value: "oldest", name: "oldest" },
];
const filterOptionList = [
{ value: "all", name: "all" },
{ value: "good", name: ":)" },
{ vlaue: "bad", name: ":(" },
];
// value = 현재 filter의 값, onChange = setFilter 필터를 바꾸는 함수, optionList = filterOptionList
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 DiaryList = ({ diaryList }) => {
const [sortType, setSortType] = useState("latest");
//모든 감정을 표현하기 위해서 기본 값 = all
const [filter, setFilter] = useState("all");
const getProcessDiaryList = () => {
const filterCallBack = (item) => {
if (filter === "good") {
// 산술 연산자 사용할거면 emotion도 숫자여야 하는데 항상 숫자일거라는 보장이 없기 때문에 parseInt로 숫자로 형변환
return parseInt(item.emotion) <= 3;
} else {
return parseInt(item.emotion) > 3;
}
};
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));
// filterCallBack에 it을 전달했을때 return ture를 반환하는 애들로만 필터링을 해라
const filteredList =
filter === "all" ? copyList : copyList.filter((it) => filterCallBack(it));
const sortedList = filteredList.sort(compare);
return sortedList;
};
return (
<div>
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
{getProcessDiaryList().map((it) => (
<div key={it.id}>
{it.content} {it.emotion}
</div>
))}
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
실행 화면
<새 일기 쓰기>
DiaryList.js
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import MyButton from "./MyButton";
const sortOptionList = [
{ value: "latest", name: "latest" },
{ value: "oldest", name: "oldest" },
];
const filterOptionList = [
{ value: "all", name: "all" },
{ value: "good", name: ":)" },
{ vlaue: "bad", 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>
);
};
const DiaryList = ({ diaryList }) => {
const navigate = useNavigate();
const [sortType, setSortType] = useState("latest");
const [filter, setFilter] = useState("all");
const getProcessDiaryList = () => {
const filterCallBack = (item) => {
if (filter === "good") {
return parseInt(item.emotion) <= 3;
} else {
return parseInt(item.emotion) > 3;
}
};
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;
};
return (
<div>
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
{/* onClick을 누르면 navigate 함수를 호출해서 /new 경로를 가지는 페이지로 페이지 이동이 가능함 */}
<MyButton
type={"positive"}
text={"write a diary"}
onClick={() => navigate("/new")}
/>
{getProcessDiaryList().map((it) => (
<div key={it.id}>
{it.content} {it.emotion}
</div>
))}
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
실행 화면
<홈 화면 css>
DiaryList.js
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import MyButton from "./MyButton";
const sortOptionList = [
{ value: "latest", name: "latest" },
{ value: "oldest", name: "oldest" },
];
const filterOptionList = [
{ value: "all", name: "all" },
{ value: "good", name: ":)" },
{ vlaue: "bad", name: ":(" },
];
const ControlMenu = ({ value, onChange, optionList }) => {
return (
<select
className="ControlMenu"
value={value}
onChange={(e) => onChange(e.target.value)}
>
{optionList.map((it, idx) => (
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
};
const DiaryList = ({ diaryList }) => {
const navigate = useNavigate();
const [sortType, setSortType] = useState("latest");
const [filter, setFilter] = useState("all");
const getProcessDiaryList = () => {
const filterCallBack = (item) => {
if (filter === "good") {
return parseInt(item.emotion) <= 3;
} else {
return parseInt(item.emotion) > 3;
}
};
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;
};
return (
<div className="DiaryList">
<div className="menu_wrapper">
<div className="left_col">
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
</div>
<div className="right_col">
<MyButton
type={"positive"}
text={"write a diary"}
onClick={() => navigate("/new")}
/>
</div>
</div>
{getProcessDiaryList().map((it) => (
<div key={it.id}>
{it.content} {it.emotion}
</div>
))}
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
실행 화면
<이미지, 글, 버튼>
DiaryItem.js
import { Navigate, useNavigate } from "react-router-dom";
import MyButton from "./MyButton";
const DiaryItem = ({ id, emotion, content, date }) => {
const navigate = useNavigate();
// 이미지가 안뜨면
const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || "";
const strDate = new Date(parseInt(date)).toLocaleDateString();
// 일기를 조회할 함수
const goDetail = () => {
navigate(`/diary/${id}`);
};
// 일기를 수정할 함수
const goEdit = () => {
navigate(`/edit/${id}`);
};
return (
<div className={"DiaryItem"}>
<div
onClick={goDetail}
className={[
"emotion_img_wrapper",
`emotion_img-wrapper_${emotion}`,
// 감정에 따라서 동적으로 className을 정해줄 수 있음
].join(" ")}
>
{/* process.env.PUBLIC_URL = public 디렉토리 주소 */}
{/* ${emotion} => 감정의 숫자가 들어옴 */}
<img src={process.env.PUBLIC_URL + `/assets/emotion${emotion}.png`} />
</div>
<div onClick={goDetail} 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 onClick={goEdit} text={"edit"} />
</div>
</div>
);
};
export default DiaryItem;
DiaryList.js
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import DiaryItem from "./DiaryItem";
import MyButton from "./MyButton";
const sortOptionList = [
{ value: "latest", name: "latest" },
{ value: "oldest", name: "oldest" },
];
const filterOptionList = [
{ value: "all", name: "all" },
{ value: "good", name: ":)" },
{ vlaue: "bad", name: ":(" },
];
const ControlMenu = ({ value, onChange, optionList }) => {
return (
<select
className="ControlMenu"
value={value}
onChange={(e) => onChange(e.target.value)}
>
{optionList.map((it, idx) => (
<option key={idx} value={it.value}>
{it.name}
</option>
))}
</select>
);
};
const DiaryList = ({ diaryList }) => {
const navigate = useNavigate();
const [sortType, setSortType] = useState("latest");
const [filter, setFilter] = useState("all");
const getProcessedDiaryList = () => {
const filterCallBack = (item) => {
if (filter === "good") {
return parseInt(item.emotion) <= 3;
} else {
return parseInt(item.emotion) > 3;
}
};
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;
};
return (
<div className="DiaryList">
<div className="menu_wrapper">
<div className="left_col">
<ControlMenu
value={sortType}
onChange={setSortType}
optionList={sortOptionList}
/>
<ControlMenu
value={filter}
onChange={setFilter}
optionList={filterOptionList}
/>
</div>
<div className="right_col">
<MyButton
type={"positive"}
text={"write a diary"}
onClick={() => navigate("/new")}
/>
</div>
</div>
{getProcessedDiaryList().map((it) => (
// DiaryItem 컴포넌트 사용할 곳
<DiaryItem key={it.id} {...it} />
))}
</div>
);
};
DiaryList.defaultProps = {
diaryList: [],
};
export default DiaryList;
실행 화면
<전체 css>
App.css
@import url("https://fonts.googleapis.com/css2?family=Nanum+Pen+Script&family=Yeon+Sung&display=swap");
body {
background-color: #f0ffff;
display: flex;
/*body태그 아래에 있는 요소들을 body 태그를 기준으로 가운데 위치 */
justify-content: center;
/*display가 flex 속성으로 있을때 세로축을 기준으로 가운데 두겠다 */
align-items: center;
/*실제 웹 스크린의 100%를 최소 높이로 갖겠다로 선언 */
font-family: "Nanum Pen Script";
margin: 0px;
min-height: 100vh;
}
/*min-width => 괄호 안에 있는 모든 css 규칙들을 웹 브라우저의 화면이 650px 이상일때만 적용*/
/* 반응형 웹을 도와주는 css 도구 */
@media (min-width: 650px) {
.App {
width: 640px;
}
}
/* 웹 브라우저의 화면이 650px 이하일 경우에 App이라는 컴포넌트는 90%를 차지하게 하겠음 */
@media (max-width: 650px) {
.App {
width: 90vw;
}
}
#root {
background-color: white;
box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px;
}
/* 기본적으로 100%를 차지하는 높이를 갖게 됨 */
.App {
min-height: 100vh;
padding-left: 20px;
padding-right: 20px;
}
/* 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-color: #ececec;
color: black;
}
.MyButton_positive {
background-color: #ffccff;
}
.MyButton_negative {
background-color: #ffe4c0;
}
/* Header */
header {
padding-top: 20px;
padding-bottom: 20px;
/* flex 속성을 주게 되면 <div> 가로로 바뀐다 */
display: flex;
align-items: center;
border-bottom: 1px solid #e2e2e2;
}
header > div {
display: flex;
}
/* justify-center:center head 텍스트 중앙에 위치 시킨다 */
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", cursive;
}
/*DiaryList*/
.DiaryList .menu_wrapper {
margin-top: 20px;
margin-bottom: 30px;
/* div 요소들이 한줄로 정렬 */
display: flex;
/* 왼쪽 div, 오른쪽 div 간격이 완전히 벌어지게 됨 */
justify-content: space-between;
}
.DiaryList .menu_wrapper .right_col {
/* display flex 아래 모든 넓이를 자신이 차지하게 됨 => 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;
}
/* 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 .info_wrapper {
flex-grow: 1;
margin-left: 20px;
cursor: pointer;
}
.DiaryItem .diary_date {
font-weight: bold;
font-size: 25px;
margin-bottom: 5px;
}
.DiaryItem .diary_content_preview {
font-size: 18px;
}
.DiaryItem .btn_wrapper {
min-width: 70px;
}
Author And Source
이 문제에 관하여(React Emotion Diary - Home), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@aloha006/ddd저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)