이 블로그는 유데미 '한입 크기로 잘라먹는 리액트' 강의를 듣고 복습하고자 작성되었습니다.
목차
1. 수정페이지(/edit)
수정페이지는 일기 작성페이지와 거의 유사해서 만드는 과정도 간단합니다.
1. id 정보와 일기 데이터 가져오기
id는 App.js의 route에서 path variable로 설정한 후 useParams를 이용해 id 객체로 불러올 것입니다.
또 일기 데이터는 일기 작성화면에서 구현했듯 useContext를 이용해 가져올 것입니다.
1-1. Edit 컴포넌트 만들기
import { useNavigate } from 'react-router-dom';
const Edit = () => {
const navigate = useNavigate();
return <div></div>;
};
export default Edit;
1-2. App.js에서 edit 페이지에서 id별로 보여주도록 path 고치기
<Route path='/edit/:id' element={<Edit />} />
1-3. useParams를 이용해 path variable을 Edit에서 받아오기
import { useNavigate, useParams } from 'react-router-dom';
const { id } = useParams();
이때 콘솔로 id를 찍어 잘 들어왔는지 확인해봅시다.
1-4. useContext로 데이터가 있는 DiaryStateContext를 불러오기
import { useContext } from 'react';
const diaryList = useContext(DiaryStateContext);
2. diaryList에서 원하는 id의 일기만 뽑기
2-1. useEffect로 일기 뽑은 후 렌더
일기를 뽑아서 렌더하는 것은, 페이지가 마운트되자 마자 이뤄져야 하는 일들입니다.
따라서 useEffect를 사용해 렌더합시다.
import { useContext, useEffect, useState } from 'react';
useEffect(() => {
if (diaryList.length >= 1) {
const targetDiary = diaryList.find(
(it) => parseInt(it.id) === parseInt(id),
);
console.log(targetDiary);
}
}, [id, diaryList]);
여기서 id나 diaryList에 변경이 있을 경우 추가로 리렌더가 필요하므로, useEffect의 두 번째 인자로 [id, diaryList] 배열을 주었습니다.
2-2. 존재하지 않는 id의 페이지를 접근할 경우, 홈으로 돌아가게 하기
useEffect(() => {
if (diaryList.length >= 1) {
const targetDiary = diaryList.find(
(it) => parseInt(it.id) === parseInt(id),
);
console.log(targetDiary);
if (targetDiary) { } else {
navigate("/", {replace:true})
}
}
}, [id, diaryList]);
아까전 코드에서 조건문을 추가, targetDiary가 없을 경우 홈화면으로 돌아가도록 합니다.
{ replace: true }는 홈 화면으로 돌아간 후 뒤로가기가 되지 않도록 하는 것입니다.
3. 뽑은 targetDiary를 저장할 state 만들기, 페이지를 잘 찾아간 경우 set state하기
import { useContext, useEffect, useState } from 'react';
const [originData, setOriginData] = useState();
if (targetDiary) {
setOriginData(targetDiary);
} else {
navigate('/', { replace: true });
}
2-2의 조건문을 수정해 targetDiary가 truthy할 경우 originData의 내용을 targetDiary로 바꿉니다.
4. return 작성
return (
<div>
{originData && <DiaryEditor isEdit={true} originData={originData} />}
</div>
);
originData가 있을 경우 이전에 만들어둔 diaryEditor 컴포넌트를 띄웁니다.
새 일기 작성이 아닌 수정 모드이고(모드에 따라 헤더 텍스트를 바꿀 예정), originData를 띄워놓은 상태에서 수정해야 하므로
해당 정보를 props로 전달합니다.
5. DiaryEditor 컴포넌트에서 props 받아 처리하기
5-1. prop 받아오기
const DiaryEditor = ({ isEdit, originData }) => {
DiaryEditor 컴포넌트의 props 부분을 수정합니다.
5-2. useEffect로 originData 띄우기
useEffect(() => {
if (isEdit) {
setDate(getStringDate(new Date(parseInt(originData.date))));
setEmotion(originData.emotion);
setContents(originData.emotion);
}
}, [isEdit, originData]);
수정 시 originData를 띄우는 건 마운트 하자마자 처리해야 하므로, useEffect를 사용합니다.
isEdit:true를 받았을 때, 즉 수정 화면에서 에디터에 진입했을 때만 이 로직이 실행됩니다.
5-3. 헤더의 headText도 isEdit 값에 따라 '새 일기쓰기'가 아니라 '수정하기'로 고치기
headText={isEdit ? '일기 수정하기' : '새 일기쓰기'}
5-4. 작성완료 버튼을 눌렀을 때, handleSubmit에서 onCreate가 아니라 수정이 되도록 하기
const { onCreate, onEdit } = useContext(DiaryDispatchContext);
먼저 useContext에서 onEdit을 불러온 후
if (
window.confirm(
isEdit ? '일기를 수정하시겠습니까?' : '새로운 일기를 작성하시겠습니까?',
)
) {
if (!isEdit) {
onCreate(date, contents, emotion);
} else {
onEdit(originData.id, date, contents, emotion);
}
}
navigate('/', { replace: true });
isEdit 상태에 따라 다른 확인메시지를 올린 후, onCreate와 onEdit 둘중 하나를 수행합니다.
2. 상세페이지(/diary)
이제 상세페이지를 구현해봅시다.
1. edit처럼 useContext로 일기 데이터를 가져와 find로 찾아 렌더
import { useContext, useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { DiaryStateContext } from '../App';
const Diary = () => {
const { id } = useParams();
const diaryList = useContext(DiaryStateContext);
const navigate = useNavigate();
const [data, setData] = useState();
useEffect(() => {
if (diaryList.length >= 1) {
const targetDiary = diaryList.find(
(it) => parseInt(it.id) === parseInt(id),
);
if (targetDiary) {
//일기가 존재할 때
setData(targetDiary);
} else {
//일기가 없을 때
alert('없는 일기입니다.');
navigate('/', { replace: true });
}
}
}, [id, diaryList]);
return (
<div>
<h1>Diary</h1>
<p>이곳은 일기 상세페이지입니다.</p>
</div>
);
};
export default Diary;
2. 일기 state를 덜 받아왔을 때 핸들링
if (!data) {
return <div className='DiaryPage'>로딩중입니다...</div>;
}
3. 바로 전 글(작성화면)에서 getStringDate를 가져와 headText 쓰기
작성화면에서 사용한 getStringDate 함수를 사용해야 하는데, 함수를 통째로 복사해오면 중복이 생겨 유지보수가 어려워집니다.
그래서 util 폴더를 만든 후 그 안에 함수를 써놓고, DiaryEditor와 Edit에서 함수를 import 해옵시다.
const getStringDate = (date) => {
const tzoffset = new Date().getTimezoneOffset() * 60000;
return new Date(Date.now() - tzoffset).toISOString().slice(0, 10);
};
이렇게 함수를 따로 써놓은 후
import { getStringDate } from './../util/date.js';
이렇게 불러오는 것입니다.
이제 return을 수정해봅시다.
if (!data) {
return <div className='DiaryPage'>로딩중입니다...</div>;
} else {
return (
<div>
<MyHeader
headText={`${getStringDate(new Date(data.date))}`}
leftChild={
<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />
}
rightChild={
<MyButton
text={'수정하기'}
onClick={() => {
navigate(`/edit/${id}`);
}}
/>
}
/>
</div>
);
}
getStringDate를 사용해줍니다.
4. 이모티콘 렌더를 위해 emotionList 불러오기
4-1. 아까처럼 util 폴더에 emotionList 배열을 작성한 후 임포트
import { emotionList } from '../util/emotion.js';
DiaryEditor.js, Diary.js에서 불러옵니다.
4-2. return 작성
if (!data) {
return <div className='DiaryPage'>로딩중입니다...</div>;
} else {
const curEmotionData = emotionList.find(
(it) => parseInt(it.emotion_id) === parseInt(data.emotion),
);
return (
<div className='DiaryPage'>
<MyHeader
headText={`${getStringDate(new Date(data.date))}`}
leftChild={
<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />
}
rightChild={
<MyButton
text={'수정하기'}
onClick={() => {
navigate(`/edit/${id}`);
}}
/>
}
/>
<article>
<section>
<h4>오늘의 감정</h4>
<div className={['diary_img_wrapper',`diary_img_wrapper_${data.emotion}`].join(' ')}>
<img src={curEmotionData.emotion_img} />
<div className='emotion_descript'>
{curEmotionData.emotion_descript}
</div>
</div>
</section>
</article>
</div>
);
}
여기서 curEmotionData가 find를 이용해 항목을 찾는 것이고,
return에 article이라는 시맨틱 태그를 추가해 curEmotionData의 이미지, 설명을 가져왔습니다.
5. 일기 내용부분 렌더
5-1. 섹션 추가
<section>
<h4>오늘의 일기</h4>
<div className='diary_contents_wrapper'>
<p>{data.contents}</p>
</div>
</section>
5-2. CSS작업
일부만 가져와봤습니다.
.DiaryPage .diary_contents_wrapper {
width: 100%;
background-color: #ececec;
border-radius: 5px;
word-break: keep-all;
overflow-wrap: break-word;
}
width 100%를 해야 자식 요소(p태그)의 text-align도 왼쪽정렬이 잘 됩니다.
또 word-break는 글 길이가 한줄을 넘어갈 때 한 단어를 쪼개지 말라는 뜻이고,
overflow-wrap은 텍스트가 한 줄을 넘어갈 경우 줄바꿈을 강제하는 것입니다.
'Udemy' 카테고리의 다른 글
[React] 프로젝트 최적화 (1) | 2023.05.27 |
---|---|
[React] 일기장 프로젝트 3. 페이지 구현 - 작성화면 (0) | 2023.05.17 |
[React] 일기장 프로젝트 - 후기 (1) | 2023.04.30 |
[React] 일기장 프로젝트 2. 페이지 구현 - 홈화면 (0) | 2023.04.25 |
[React] 일기장 프로젝트 1. 기초 작업 (0) | 2023.04.25 |