/NEW
1. 일기쓰기
헤더, 시간 선택, 감정 선택, 일기 쓰기, 버튼을 구현한다.
1) 헤더
- New.js
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)} />}
/>
</div>
);
};
export default New;
useNavigate() 의 반환값으로 받은 함수의 매개변수로 '-1' 을 넣어준다.
2) 시간 선택
달력을 띄워 날짜를 선택하는 입력창을 구현한다. section 은 의미론적 태그로써 역할은 div 태그와 똑같지만 이름만 다르다. input 태그의 type date 는 mac 의 safari 나 인터넷 익스플로러에서는 동작하지 않을 수 있기 때문에, 크롬, 엣지, 웨일, 오페라에서 확인하기.
오늘 날짜가 달력의 초기값이 되도록 설정한다.
- New.js
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import MyHeader from './../components/MyHeader';
import MyButton from './../components/MyButton';
const getStringDate = (date) => {
return date.toISOString().slice(0, 10); // ISO 형식의 문자열을 반환
};
const New = () => {
// 페이지 이동
const navigate = useNavigate();
// 시간 state
const [date, setDate] = useState(getStringDate(new Date()));
return (
<div>
<MyHeader
headText={'새로운 일기 쓰기'}
leftChild={<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />}
/>
<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;
toISOString() 메서드는 ISO 형식의 문자열을 반환하는데, 앞의 10 자를 잘라와서 사용한다.
참고링크 : https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
Date.prototype.toISOString() - JavaScript | MDN
toISOString() 메서드는 단순화한 확장 ISO 형식(ISO 8601)의 문자열을 반환합니다. 반환값은 언제나 24글자 또는 27글자(각각 YYYY-MM-DDTHH:mm:ss.sssZ 또는 ±YYYYYY-MM-DDTHH:mm:ss.sssZ)입니다.시간대는 언제나 UTC이
developer.mozilla.org
일기쓰기 페이지와 일기수정 페이지가 동일한 패턴이므로, 새로운 컴포넌트를 생성하여 동일한 부분을 독립하여 구현한다. 이후 일기쓰기 페이지와 일기수정 페이지에서 독립된 컴포넌트를 사용하도록 한다.
src\components\DiaryEditor.js 파일을 생성하여 구현한다.
- DiaryEditor.js
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import MyHeader from './MyHeader';
import MyButton from './MyButton';
// 현재시간 가져오기
const getStringDate = (date) => {
return date.toISOString().slice(0, 10); // ISO 형식의 문자열을 반환
};
const DiaryEditor = () => {
// 페이지 이동
const navigate = useNavigate();
// 시간 state
const [date, setDate] = useState(getStringDate(new Date()));
return (
<div className='DiaryEditor'>
<MyHeader
headText={'새로운 일기 쓰기'}
leftChild={<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />}
/>
<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;
- New.js
import DiaryEditor from "../components/DiaryEditor";
const New = () => {
return (
<div>
<DiaryEditor />
</div>
);
};
export default New;
DiaryEditor 컴포넌트에 동일한 부분을 독립시켰다.
DiaryEditor 컴포넌트의 시간 선택하는 부분에 스타일을 적용한다.
- App.css
/* DiaryEditor */
.DiaryEditor {
}
.DiaryEditor section {
margin-bottom: 40px;
}
.DiaryEditor h4 {
font-size: 22px;
font-weight: bold;
}
.DiaryEditor .input_date {
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: 20px;
}
3) 감정 선택
감정 데이터 배열을 만들어 props 로 EmotionItem 컴포넌트에 전달하여 감정 목록을 출력한다. 이후 감정 목록의 스타일을 적용한 뒤, 선택하는 기능을 구현하고 선택 되었을 때 스타일을 적용한다.
- DiaryEditor.js
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import MyHeader from './MyHeader';
import MyButton from './MyButton';
import EmotionItem from './EmotionItem';
// 현재시간 가져오기
const getStringDate = (date) => {
return date.toISOString().slice(0, 10); // ISO 형식의 문자열을 반환
};
// 감정 데이터 배열
const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || ''; // rocess.env 미작동 방지
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 navigate = useNavigate();
// 시간 state
const [date, setDate] = useState(getStringDate(new Date()));
// 감정 state
const [emotion, setEmotion] = useState(3);
// 감정 클릭시 실행되어 props 로 전달하는 함수
const handleClickEmote = (emotion) => {
setEmotion(emotion);
}
return (
<div className='DiaryEditor'>
<MyHeader
headText={'새로운 일기 쓰기'}
leftChild={<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />}
/>
<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) => (
<EmotionItem
key={it.emotion_id}
{...it}
onClick={handleClickEmote}
isSelected={it.emotion_id === emotion}
/>
))}
</div>
</section>
</div>
</div>
);
}
export default DiaryEditor;
- 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;
- App.css
...
.DiaryEditor .emotion_list_wrapper {
display: grid; /* 그리드 만드는 속성 */
grid-template-columns: repeat(5, auto); /* 5개 열을 나열하고, 사이즈는 자동 */
gap: 2%; /* 그리드 내의 item 사이 gap */
}
/* EmotionItem */
.EmotionItem {
cursor: pointer;
border-radius: 5px;
padding-top: 20px;
padding-bottom: 20px;
display: flex;
flex-direction: column; /* 그림 아래로 text 내림 */
justify-content: center; /* 세로의 중앙 */
align-items: center; /* 정사각형 박스가 있을 때 가장 중앙에 Element 위치 시킴 */
}
.EmotionItem img {
width: 50%;
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;
}
...
4) 일기 쓰기
textarea 태그를 이용하여 구현한 뒤 스타일을 적용한다.
- DiaryEditor.js
import { useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import MyHeader from './MyHeader';
import MyButton from './MyButton';
import EmotionItem from './EmotionItem';
// 현재시간 가져오기
const getStringDate = (date) => {
return date.toISOString().slice(0, 10); // ISO 형식의 문자열을 반환
};
// 감정 데이터 배열
const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || ''; // rocess.env 미작동 방지
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 navigate = useNavigate();
// 시간 state
const [date, setDate] = useState(getStringDate(new Date()));
// 감정 state
const [emotion, setEmotion] = useState(3);
// 감정 클릭시 실행되어 props 로 전달하는 함수
const handleClickEmote = (emotion) => {
setEmotion(emotion);
}
// 일기 state
const [content, setContent] = useState('');
// 포커싱 위한 레퍼런스
const contentRef = useRef();
return (
<div className='DiaryEditor'>
<MyHeader
headText={'새로운 일기 쓰기'}
leftChild={<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />}
/>
<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) => (
<EmotionItem
key={it.emotion_id}
{...it}
onClick={handleClickEmote}
isSelected={it.emotion_id === emotion}
/>
))}
</div>
</section>
<section>
<h4>오늘의 일기</h4>
<div className='input_box text_wrapper'>
<textarea
placeholder='오늘은 어땠나요?'
ref={contentRef}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</div>
</section>
</div>
</div>
);
}
export default DiaryEditor;
- App.css
...
.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: #e2e2e2;
padding: 20px;
}
...
5) 버튼
취소하기, 작성완료 버튼을 만든 뒤, 각 기능을 구현하고 스타일을 적용한다.
- DiaryEditor.js
import { useContext, useRef, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import MyHeader from './MyHeader';
import MyButton from './MyButton';
import EmotionItem from './EmotionItem';
import { DiaryDispatchContext } from '../App';
// 현재시간 가져오기
const getStringDate = (date) => {
return date.toISOString().slice(0, 10); // ISO 형식의 문자열을 반환
};
// 감정 데이터 배열
const env = process.env;
env.PUBLIC_URL = env.PUBLIC_URL || ''; // rocess.env 미작동 방지
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 navigate = useNavigate();
// 시간 state
const [date, setDate] = useState(getStringDate(new Date()));
// 감정 state
const [emotion, setEmotion] = useState(3);
// 감정 클릭시 실행되어 props 로 전달하는 함수
const handleClickEmote = (emotion) => {
setEmotion(emotion);
}
// 일기 state
const [content, setContent] = useState('');
// dispatch context 로 공급되는 onCreate 함수 가져오기
const { onCreate } = useContext(DiaryDispatchContext);
// 포커싱 위한 레퍼런스
const contentRef = useRef();
// 작성완료 버튼 클릭시 실행 함수
const handleSubmit = () => {
if (content.length < 1) {
contentRef.current.focus();
return;
}
onCreate(date, content, emotion);
navigate('/', { replace: true }); // 일기 작성페이지에서 뒤로가게 하여 못오게 막음
}
return (
<div className='DiaryEditor'>
<MyHeader
headText={'새로운 일기 쓰기'}
leftChild={<MyButton text={'< 뒤로가기'} onClick={() => navigate(-1)} />}
/>
<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) => (
<EmotionItem
key={it.emotion_id}
{...it}
onClick={handleClickEmote}
isSelected={it.emotion_id === emotion}
/>
))}
</div>
</section>
<section>
<h4>오늘의 일기</h4>
<div className='input_box text_wrapper'>
<textarea
placeholder='오늘은 어땠나요?'
ref={contentRef}
value={content}
onChange={(e) => setContent(e.target.value)}
/>
</div>
</section>
<section>
<div className='control_box'>
<MyButton text={'취소하기'} onClick={() => navigate(-1)} />
<MyButton text={'작성완료'} type={'positive'} onClick={handleSubmit} />
</div>
</section>
</div>
</div>
);
}
export default DiaryEditor;
- App.css
...
.DiaryEditor .control_box {
display: flex;
justify-content: space-between; /* 각 요소 양쪽으로 벌어짐 */
align-items: center; /* 세로축 기준 중앙으로 맞춤 */
}
...
참고강의 : https://www.inflearn.com/course/%ED%95%9C%EC%9E%85-%EB%A6%AC%EC%95%A1%ED%8A%B8#
한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지 강의 - 인프런
개념부터 독특한 프로젝트까지 함께 다뤄보며 자바스크립트와 리액트를 이 강의로 한 번에 끝내요. 학습은 짧게, 응용은 길게 17시간 분량의 All-in-one 강의!, 리액트, 한 강의로 끝장낼 수 있어요.
www.inflearn.com
'강의 실습 > 한입 크기로 잘라 먹는 리액트(React.js) : 기초부터 실전까지' 카테고리의 다른 글
페이지 구현 - 일기 상세 (/diary) (2) | 2024.02.10 |
---|---|
페이지 구현 - 일기 수정 (/edit) (2) | 2024.02.09 |
페이지 구현 - 홈 (/) (4) | 2024.02.04 |
프로젝트 기초 공사 2 (0) | 2024.02.03 |
프로젝트 기초 공사 1 (0) | 2024.02.03 |
댓글