[React] 페이지 구현 - New(‘/new’)
1️⃣ 헤더 만들기
헤더는 기존에 만들어두었던 MyHeader 컴포넌트를 사용을 한다.
import { useNavigate } from "react-router-dom";
import MyHeader from "../components/MyHeader";
import MyButton from "../components/MyButton";
const New = () => {
const navigate = useNavigate();
return (
<div>
<MyHeader
headText={"새 일기쓰기"}
leftChild={
<MyButton
text={"< 뒤로 가기"}
onClick={() => navigate(-1)}
></MyButton>
}
></MyHeader>
</div>
);
};
export default New;
2️⃣ 오늘은 언제인가요?
이 부분은 input 태그의 type=”date” 속성과 속성값을 사용한다. 그리고 만약 값이 변경된다면 그 값을 date로 불러오는 것이다.
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import MyHeader from "../components/MyHeader";
import MyButton from "../components/MyButton";
const New = () => {
const [date, setDate] = useState();
const navigate = useNavigate();
return (
<div>
<MyHeader
headText={"새 일기쓰기"}
leftChild={
<MyButton
text={"< 뒤로 가기"}
onClick={() => navigate(-1)}
></MyButton>
}
></MyHeader>
<div>
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input-box">
<input
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
</section>
</div>
</div>
);
};
export default New;
❓근데 여기서 value는 무슨 역할을 하는거야?
여튼 이렇게 한다면 틀은 어느 정도 완성되었다. 이제부터는 좀더 디테일하게 가서 새로쓰기 버튼을 눌렀을 때, input 태그에 “연도-월-일”로 들어가있는 것이 아닌 현재 날짜가 들어가 있도록 해보는 것이다.
// src > pages > New.js
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import MyHeader from "../components/MyHeader";
import MyButton from "../components/MyButton";
const getStringDate = (date) => {
return date.toISOString().slice(0, 10);
};
const New = () => {
const [date, setDate] = useState(getStringDate(new Date()));
const navigate = useNavigate();
return (
<div>
<MyHeader
headText={"새 일기쓰기"}
leftChild={
<MyButton
text={"< 뒤로 가기"}
onClick={() => navigate(-1)}
></MyButton>
}
></MyHeader>
<div>
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input-box">
<input
className="input-date"
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
</section>
</div>
</div>
);
};
export default New;
getStringDate라는 함수를 통해서 ms 단위로 받아온 현재 날짜를 YYYY-MM-DD 형식으로 리턴한다. 그리고 useState의 디폴트 값을 리턴받은 값으로 사용하면 된다.
그리고 위에 헤더와 오늘의 날짜를 입력하는 부분은 수정하는 페이지와 형식이 동일하므로 일단 따로 컴포넌트로 빼서 DiaryEditor 컴포넌트를 만들어 준다.
그리고 DiaryEditor 컴포넌트를 new 컴포넌트에 넣어준다.
// src > components > DiaryEditor.js
import { useNavigate } from "react-router-dom";
import { useState } from "react";
import MyHeader from "./MyHeader";
import MyButton from "./MyButton";
const getStringDate = (date) => {
return date.toISOString().slice(0, 10);
};
const DiaryEditor = () => {
const [date, setDate] = useState(getStringDate(new Date()));
const navigate = useNavigate();
return (
<div>
<MyHeader
headText={"새 일기쓰기"}
leftChild={
<MyButton
text={"< 뒤로 가기"}
onClick={() => navigate(-1)}
></MyButton>
}
></MyHeader>
<div>
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input-box">
<input
className="input-date"
type="date"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
</section>
</div>
</div>
);
};
export default DiaryEditor;
/// src > pages >New.js
import DiaryEditor from "../components/DiaryEditor";
const New = () => {
return (
<div>
<DiaryEditor></DiaryEditor>
</div>
);
};
export default New;
3️⃣ 오늘의 감정
emotoinList를 만들어서 반복문을 돌리면서 이미지를 출력해준다.
// src> components > DiaryEditor.js
const emotionList = [
{
emotion_id: 1,
emotion_img: process.env.PUBLIC_URL + `/assets/emotion1.png`,
emotion_descript: "완전 좋음",
},
{
emotion_id: 2,
emotion_img: process.env.PUBLIC_URL + `/assets/emotion2.png`,
emotion_descript: "좋음",
},
{
emotion_id: 3,
emotion_img: process.env.PUBLIC_URL + `/assets/emotion3.png`,
emotion_descript: "그럭저럭",
},
{
emotion_id: 4,
emotion_img: process.env.PUBLIC_URL + `/assets/emotion4.png`,
emotion_descript: "나름",
},
{
emotion_id: 5,
emotion_img: process.env.PUBLIC_URL + `/assets/emotion5.png`,
emotion_descript: "끔찍함",
},
]
...
const DiaryEditor = () => {
const [date, setDate] = useState(getStringDate(new Date()));
...
return (
<div className="DiaryEditor">
<MyHeader headText={"새 일기쓰기"}
leftChild={<MyButton text={"< 뒤로 가기"} onClick={() => navigate(-1)}></MyButton>}></MyHeader>
<div>
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input_box">
<input className="input_date" type="date" value={date} onChange={(e) => setDate(e.target.value)} />
</div>
</section>
<section>
<h4>오늘의 감정</h4>
<div className="input_box emotion_list_wrapper">
{emotionList.map((it) => (
<div key={it.emotion_id}>{it.emotion_descript}</div>
))}
</div>
</section>
</div>
</div>
);
}
여기서 몇 점의 감정 점수를 클릭했는지를 알아내야하므로 따로 컴포넌트를 분리해서 DiaryEditor.js 컴포넌트를 만들어 준다.
// src > components > EmotionItem.js
const EmotionItem = ({ emotion_id, emotion_img, emotion_descript }) => {
return (
<div>
<img src={emotion_img} />
<span>{emotion_descript}</span>
</div>
);
};
export default EmotionItem;
이 컴포넌트를 사용할 수 있어야 하므로
// src > component > DiaryEditor.js
<section>
<h4>오늘의 감정</h4>
<div className="input_box emotion_list_wrapper">
{emotionList.map((it) => (
<EmotionItem key={it.emotion_id} {...it}></EmotionItem>
))}
</div>
</section>
EmotionItem에 맞는 프롭을 전달해준다.
그런다음 EmotionItem 컴포넌트의 디자인을 수정해주어서 데모랑 배치를 똑같이 만들어준다.
.DiaryEditor .emotion_list_wrapper {
display: grid;
grid-template-columns: repeat(5, auto);
gap: 2%;
}
/* EmotionItem */
.EmotionItem {
cursor: pointer;
border-radius: 5px;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.EmotionItem img {
width: 50%;
margin-bottom: 10px;
}
.EmotionItem span {
font-size: 18px;
}
그런 다음 버튼을 클릭했을 때, 누른 감정 이미지에 해당하는 점수를 입력받아야 한다. 그러기 위해서는 EmotionItem 컴포넌트의 이미지를 클릭하면(onClick) 해당 id를 가져와서 -> DiaryEditor에서 useState를 사용해서 emotion_id를 인지한다.
// src > components > EmotionItem.js
const EmotionItem = ({
emotion_id,
emotion_img,
emotion_descript,
onClick,
}) => {
return (
<div className="EmotionItem" onClick={() => onClick(emotion_id)}>
<img src={emotion_img} />
<span>{emotion_descript}</span>
</div>
);
};
export default EmotionItem;
// src > components > DiaryEditor.js
...
const handleClickRemote = (emotion) => {
setEmotion(emotion);
}
...
<section>
<h4>오늘의 감정</h4>
<div className="input_box emotion_list_wrapper">
{emotionList.map((it) => (
<EmotionItem key={it.emotion_id} {...it}onClick={handleClickRemote}></EmotionItem>
))}
</div>
</section>
그런 다음 선택이 되었다면 해당 점수 색깔을 아니라면 회색 배경색을 주어야 하니까 현재 선택된 감정 이미지가 무엇인지를 알아야 한다.
// src > components > DiaryEditor.js
<section>
<h4>오늘의 감정</h4>
<div className="input_box emotion_list_wrapper">
{emotionList.map((it) => (
<EmotionItem
key={it.emotion_id}
{...it}
onClick={handleClickRemote}
isSelected={it.emotion_id === emotion}
></EmotionItem>
))}
</div>
</section>
isSelected 점수를 전달해서 클릭이 되었다면 true, 아니라면 false를 EmotionItem 컴포에 전달을 해준다.
EmotionItem에서는 전달받은 isSelected 프롭을 가지고 true라면 on className을 false라면 off className을 지정한다.
// src > components > EmotionItem.js
const EmotionItem = ({
emotion_id,
emotion_img,
emotion_descript,
onClick,
isSelected,
}) => {
return (
<div
className={[
"EmotionItem",
isSelected ? `EmotionItem_on_${emotion_id}` : `EmotionItem_off`,
].join(" ")}
onClick={() => onClick(emotion_id)}
>
<img src={emotion_img} />
<span>{emotion_descript}</span>
</div>
);
};
export default EmotionItem;
on/off css
.EmotionItem span {
font-size: 18px;
}
.EmotionItem_off {
background-color: #ececec;
}
.EmotionItem_on_1 {
background-color: #64c964;
}
.EmotionItem_on_2 {
background-color: #9dd772;
}
.EmotionItem_on_3 {
background-color: #fdce17;
}
.EmotionItem_on_4 {
background-color: #fd8446;
}
.EmotionItem_on_5 {
background-color: #fd585f;
}
4️⃣ 오늘의 일기 작성
// src > components > DiaryEditor.js
return
...
<section>
<h4>오늘의 일기</h4>
<div className="input_box text_wrapper">
<textarea
placeholder="오늘은 어땠나요?"
ref={contentRef} value={content} onChange={(e) => setContent(e.target.value)}></textarea>
</div>
</section>
그리구 스타일링
.DiaryEditor textarea {
font-family: "Nanum Pen Script";
font-size: 20px;
box-sizing: border-box;
width: 100%;
min-height: 200px;
resize: vertical;
border: none;
border-radius: 5px;
background-color: #ececec;
padding: 20px;
}
5️⃣ 취소하기, 작성완료 버튼
버튼 자체를 만드는 것은 쉽다! MyButton 컴포를 사용하고, 그거를 묶고 있는 div를 display: flex 시켜주면 되기때문이다. 여차저차 해서 기능 추가하는 것이 어려웠다.
우선 작성완료가 클릭이 되면 App 컴포넌트에 있던 onCreate 함수를 사용해주어야 한다. 이를 사용하기 위해서는 useContext를 사용해서 드릴링을 막는다. 여튼 사용을 하고, DiaryEditor 컴포넌트에서 onCreate함수를 사용해서 데이터를 저장을 해준다. 데이터 저장이 되었으면 navigate함수를 통해서 ‘/’ 홈 부분으로 자동으로 이동시켜 준다.
// src > components > DiaryEditor.js
...
const {onCreate} = useContext(DiaryDispatchContext);
...
const navigate = useNavigate();
const handleSubmit = () => {
if (content.length < 1) {
content.current.focus();
return;
}
onCreate(date, content, emotion);
navigate('/', {replace: true})
}
...
<section>
<div className="control_box">
<MyButton text={"취소하기"} onClick={() => navigate(-1)}></MyButton>
<MyButton text={"작성완료"} type={"positive"} onClick={handleSubmit}></MyButton>
</div>
</section>
댓글남기기