인프런 이정한님의 한입크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 ] 강의 수강 후 강의의 내용을 정리하며 공부한 것을 쓴 게시글입니다.
페이지 구현 - 일기쓰기 ( /New )
- 일기 새로 쓰기 기능과 일기 수정하기 기능의 editor 부분은 동일하기 때문에 DiaryEditor 컴포넌트를 생성하고 각각의 컴포넌트에서 불러오기 할 것
Header
- < 버튼은 뒤로가기 기능
<div>
<MyHeader
headText={"새 일기쓰기"}
leftChild={<MyButton text={"<"} onClick={() => navigate(-1)} />}
/>
</div>
날짜 선택 section
- 일기 작성하는 날짜는 state로 관리
- 기본 값은 현재 날짜인 momnet()인데, datetime-local input 과 형식을 맞추기 위해 포맷 설정함
const [date, setDate] = useState(new moment().format("YYYY-MM-DDThh:mm"));
- type이 datetime-local인 input태그를 추가해 날짜와 시간을 선택할 수 있도록 함
- 기본값이 현재 날짜와 시간이므로 따로 설정하지 않으면 현재 시간이 뜸
<section>
<h4>오늘은 언제인가요?</h4>
<div className="input_box">
<input
className="input_date"
type="datetime-local"
value={date}
onChange={(e) => setDate(e.target.value)}
/>
</div>
</section>
감정 선택 section
- emotionList 배열 생성
const emotionList = [
{
emotion_id: 1,
emotion_img: process.env.PUBLIC_URL + "/assets/emotion1.png",
emotion_description: "완전좋아요",
},
{
emotion_id: 2,
emotion_img: process.env.PUBLIC_URL + "/assets/emotion2.png",
emotion_description: "좋아요",
},
{
emotion_id: 3,
emotion_img: process.env.PUBLIC_URL + "/assets/emotion3.png",
emotion_description: "그저그래요",
},
{
emotion_id: 4,
emotion_img: process.env.PUBLIC_URL + "/assets/emotion4.png",
emotion_description: "나빠요",
},
{
emotion_id: 5,
emotion_img: process.env.PUBLIC_URL + "/assets/emotion5.png",
emotion_description: "끔찍해요",
},
];
- 현재 선택된 emotion을 state로 관리
const [emotion, setEmotion] = useState(1);
- 각각의 emotion 선택 버튼을 EmotionItem 컴포넌트를 생성해 관리하고, DiaryEditor에서 불러옴
<EmotionItem
key={it.emotion_id}
{...it}
onClick={handleClickEmotion}
isSelected={it.emotion_id === emotion}
/>
- 선택된 감정으로 setEmotion해주는 함수 handleClickEmotion 선언하고 onClick 함수로 지정
const handleClickEmotion = (emotion) => {
setEmotion(emotion);
};
- EmotionItem 컴포넌트
- 감정에 따라서 선택되었을 때 background 색이 다르기 때문에, className으로 emotion_img_wrapper_{emtoion}을
추가로 설정
const EmotionItem = ({
emotion_id,
emotion_img,
emotion_description,
onClick,
isSelected,
}) => {
return (
<div
onClick={() => onClick(emotion_id)}
className={[
"emotion_img_wrapper",
"emotion_img_wrapper_" + emotion,
].join(" ")}
>
<img src={emotion_img} alt="face" />
<span>{emotion_description}</span>
</div>
);
};
export default EmotionItem;
- EmotionItem 스타일링
/* EmotionItem */
.EmotionItem {
cursor: pointer;
border-radius: 5px;
padding: 20px 0px 20px 0px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.EmotionItem img {
width: 60%;
margin-bottom: 10px;
}
.EmotionItem span {
font-size: 18px;
}
.EmotionItem_off {
background-color: #ececec;
}
.EmotionItem_on_1 {
background-color: #64c964;
color: white;
}
.EmotionItem_on_2 {
background-color: #9dd772;
color: white;
}
.EmotionItem_on_3 {
background-color: #fdce17;
color: white;
}
.EmotionItem_on_4 {
background-color: #fd8446;
color: white;
}
.EmotionItem_on_5 {
background-color: #fd565f;
color: white;
}
일기 작성 section
- DiaryEditor 컴포넌트에 일기를 작성할 textarea 태그가 있는 section 추가
<section>
<h4>오늘의 일기를 작성해주세요.</h4>
<div className="text_wrapper">
<textarea
placeholder="오늘은 어땠나요?"
ref={contentRef}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</div>
</section>
- textarea 스타일링 하기
.DiaryEditor textarea {
font-family: "Nanum Myeongjo", serif;
font-size: 20px;
width: 100%;
box-sizing: border-box;
min-height: 200px;
resize: vertical;
border: none;
background-color: #ececec;
padding: 20px;
}
완료 section
- 작성한 일기의 길이가 1미만일 떄 textarea에 focus를 주기 위한 Ref 추가
- 현재 일기의 content는 state로 관리
const contentRef = useRef();
const [content, setContent] = useState("");
- DiaryEditor에 완료 button이 있는 section 추가
<section>
<div className="submit_btn">
<MyButton text={"완료"} type="positive" onClick={handleSubmit} />
</div>
</section>
- DiaryDispatchContext 에서 onCreate함수를 불러옴
- 완료 버튼을 클릭했을때 처리할 함수 handleSubmit 함수 선언
- 길이가 1미만일 때 (작성한 내용이 없을 때) textarea에 focus 주며 return
- 작성한 내용이 있을 때 홈 화면으로 이동 ( replace:true 설정하면 다시 뒤로가기 불가)
- onCreate 함수에 date, content, emotion 전달
const { onCreate } = useContext(DiaryDispatchContext);
const handleSubmit = () => {
if (content.length < 1) {
contentRef.current.focus();
return;
}
navigate("/", { replace: true });
onCreate(date, content, emotion);
};
페이지 구현 - 일기 수정( /Edit )
Edit 컴포넌트
- 일기 리스트에서 수정하기 버튼을 누르면 /Edit/{id} 로 이동함
- Edit 컴포넌트에서 id 값 받아오기
/* App.js */
<Route path="/edit/:id" element={<Edit />} />
/* Edit 컴포넌트 */
const { id } = useParams();
- Edit 컴포넌트에서 diaryList 불러오기
const diaryList = useContext(DiaryStateContext);
- 수정 중인 일기의 원본 originData를 state로 관리
const [originData, setOriginData] = useState();
- useEffect로 diaryList나 id값이 변경되었을 때 아래 함수 실행되도록 함
- diaryList의 길이가 0보다 크지 않으면 (없으면) 홈 화면으로 이동하게 함
- diaryList의 길이가 0보다 크면 diaryList 중 id값이 수정 중인 다이어리의 id와 같은 것을 찾아 targetDiary에 저장함
- targetDiary가 있으면 setOriginData하고, 없으면 홈 화면으로 이동
useEffect(() => {
if (diaryList.length > 0) {
const targetDiary = diaryList.find(
(it) => parseInt(id) === parseInt(it.id)
);
if (targetDiary) {
setOriginData(targetDiary);
} else {
navigate("/", { replace: true });
}
} else {
navigate("/", { replace: true });
}
}, [diaryList, id]);
- originData가 truty면 DiaryEditor 컴포넌트에 isEdit:true와 originData를 전달하여 호출함
return (
<div>
{originData && <DiaryEditor isEdit={true} originData={originData} />}
</div>
);
DiaryEditor 컴포넌트 수정
- handleSubmit 함수 수정
- isEdit은 수정 중인지, 새 일기 작성 중인지를 구분하기 위한 변수
- isEdit이 true이면 " 수정된 일기를 저장할까요?"를 confirm으로 띄우고, false이면 " 새 일기를 저장힐까요?"를 confirm으로 띄움
- 사용자가 확인 버튼을 눌렀을 떄 isEdit이 true이면 onEdit에 originDate.id, date,content,emotion을 전달
- false이면 onCreate에 date, content, emotion을 전달
const handleSubmit = () => {
if (content.length < 1) {
contentRef.current.focus();
return;
}
if (window.confirm((isEdit ? "수정된 " : "새 ") + "일기를 저장할까요?")) {
if (isEdit) {
onEdit(originData.id, date, content, emotion);
} else {
onCreate(date, content, emotion);
}
}
navigate("/", { replace: true });
};
- useEffect 로 isEdit 값과 originData값이 변경될 때마다 아래 함수 실행되도록 함
- isEdit이 true 이면 수정하는 중이므로, 에디터의 날짜와 감정, 내용을 원본으로 띄우도록 함
useEffect(() => {
if (isEdit) {
setDate(moment(originData.date).format("YYYY-MM-DDThh:mm"));
setEmotion(originData.emotion);
setContent(originData.content);
}
}, [isEdit, originData]);
- 헤더 수정하기
<MyHeader
headText={isEdit ? "일기 수정하기" : "새 일기 작성하기"}
leftChild={<MyButton text={"<"} onClick={() => navigate(-1)} />}
/>
페이지 구현 - 일기 상세 ( /Dairy )
- 홈 화면에서 다이어리 아이템을 클릭하면 해당 일기의 상세 페이지 (/Diary/{id}) 로 이동함
- id값을 받아오기 위해 useParams 사용하고, diaryLlist를 DiaryStateContext에서 받아옴
const { id } = useParams();
const diaryList = useContext(DiaryStateContext);
- 상세페이지를 보여줄 다이어리 data를 state로 관리
const [data, setData] = useState();
- useParams로 받은 id값과 같은 id를 가지고 있는 다이어리를 find로 찾아서 targetDiary에 저장하고, setData함
- 없으면 home화면으로 이동
useEffect(() => {
if (diaryList.length > 0) {
const targetDiary = diaryList.find(
(it) => parseInt(id) === parseInt(it.id)
);
if (targetDiary) {
setData(targetDiary);
} else {
alert("존재하지 않는 일기입니다.");
navigate("/", { replace: true });
}
}
}, [id, diaryList]);
- emotionList에서 현재 다이어리의 emotion값을 find해서 curEmotionData에 저장
const curEmotionData = emotionList.find(
(it) => parseInt(it.emotion_id) === parseInt(data.emotion)
);
헤더 secrion
- 헤더의 text를 현재 날짜로 하고, 왼쪽에는 뒤로가기 버튼을, 오른쪽에는 수정하기 버튼을 추가함
<MyHeader
headText={`${moment(data.date).format("YYYY년 MM월 DD일 dddd")}`}
leftChild={<MyButton text="<" onClick={() => navigate(-1)} />}
rightChild={
<MyButton
text="수정하기"
onClick={() => navigate("/edit/" + data.id)}
/>
}
/>
감정 section
- 헤더 바로 아래에 오늘의 감정을 이미지와 text로 띄움
<section>
<h4>오늘의 감정</h4>
<div
className={[
"diary_img_wrapper",
`diary_img_wrapper_${data.emotion}`,
].join(" ")}
>
<img src={curEmotionData.emotion_img} />
<div className="emotion_description">
<span>{curEmotionData.emotion_description}</span>
</div>
</div>
</section>
- 감정 section 스타일링
.Diary .diary_img_wrapper {
background-color: #ececec;
width: 250px;
height: 250px;
border-radius: 5px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-around;
}
.Diary .diary_img_wrapper_1 {
background-color: #64c964;
color: white;
}
.Diary .diary_img_wrapper_2 {
background-color: #9dd772;
color: white;
}
.Diary .diary_img_wrapper_3 {
background-color: #fdce17;
color: white;
}
.Diary .diary_img_wrapper_4 {
background-color: #fd8446;
color: white;
}
.Diary .diary_img_wrapper_5 {
background-color: #fd565f;
color: white;
}
.Diary .emotion_description {
font-size: 20px;
}
내용 section
<section>
<h4>오늘의 일기</h4>
<div className="diary_content_wrapper">
<p>{data.content}</p>
</div>
</section>
'FRONTEND > React' 카테고리의 다른 글
[ 인프런 - 한입 크기로 잘라 먹는 React] 3. React 실전 프로젝트 - 감정 일기장 만들기 최종 결과물 (0) | 2023.01.15 |
---|---|
[ 인프런 - 한입 크기로 잘라 먹는 React] 3. React 실전 프로젝트 - 감정 일기장 만들기(4) (1) | 2023.01.11 |
[ 인프런 - 한입 크기로 잘라 먹는 React] 3. React 실전 프로젝트 - 감정 일기장 만들기(2) (0) | 2023.01.09 |
[ 인프런 - 한입 크기로 잘라 먹는 React] 3. React 실전 프로젝트 - 감정 일기장 만들기(1) (0) | 2023.01.08 |
[ 인프런 - 한입 크기로 잘라 먹는 React] 2. React 기본 - 간단한 일기장 프로젝트 (2) (0) | 2023.01.06 |