밍비
프로그램의 편린
밍비
전체 방문자
오늘
어제
  • 분류 전체보기 (64)
    • Spring (2)
    • TIL (23)
    • 프로그래머스 (12)
    • Udemy (16)
    • Typescript (2)
    • MERN (1)
    • AWS (7)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

인기 글

태그

  • useRef
  • 리액트 reducer
  • 분산저장소
  • Page Moving
  • 서비스아키텍처
  • State 합치기
  • 리스트 조회
  • 한입크기로잘라먹는리액트
  • Points of Presence
  • 리액트
  • DOM
  • 리액트 프로젝트 만들기
  • 수평 스케일링
  • API 호출
  • useNavigate
  • useParams
  • 컴포넌트트리
  • Edge Locations
  • 한입 크기로 잘라먹는 리액트
  • state 끌어올리기
  • AWS Regions
  • overflow-wrap
  • 네이버커넥트
  • 데이터 수정
  • 함수형 update
  • 리액트 생애주기
  • useState
  • state 관리
  • Availability Zones
  • react

최근 댓글

최근 글

티스토리

hELLO · Designed By 정상우.
밍비
Udemy

[React] 일기장 프로젝트 3. 페이지 구현 - 작성화면

[React] 일기장 프로젝트 3. 페이지 구현 - 작성화면
Udemy

[React] 일기장 프로젝트 3. 페이지 구현 - 작성화면

2023. 5. 17. 16:31
728x90

이 블로그는 유데미 '한입 크기로 잘라먹는 리액트' 강의를 듣고 복습하고자 작성되었습니다.

 

목차

1. New.js 작성

2. DiaryEditor.js

3. 감정 선택 섹션

4. 오늘의 일기 작성 섹션

두 번째로 만들 화면은 새 일기 작성 화면입니다.

이전에 만들어뒀던 컴포넌트를 이용해 헤더, 날짜, 감정 선택 섹션, 제출까지 작업해보겠습니다.

또한, 일기 작성화면과 수정 화면이 유사하므로 새 컴포넌트를 만들어 내용을 옮기는 작업도 해보겠습니다.

 

1. New.js 작성

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;

뒤로가기 버튼의 navigate(-1)은 뒤로가기 기능을 수행합니다.

 

2. 날짜 묻는 부분

<div>
  <section>
    <h4>오늘은 언제인가요?</h4>
    <div className='input-box'>
      <input type='date' />
    </div>
  </section>
</div>

여기서 section은 시맨틱 태그로, div와 하는 일이 같습니다.

또 input 태그에서 type='date'로 할 경우 날짜선택 창이 됩니다!

 

3. 날짜 state 추가, 리턴의 input 태그에 value와 onChange 추가

import React, { useState } from 'react';
const [date, setDate] = useState();
<input
  type='date'
  value={date}
  onChange={(e) => setDate(e.target.value)}
/>

 

4. 날짜 input 부분의 기본값으로 오늘 날짜 넣기

const getStringDate = (date) => {
  return date.toISOString().slice(0, 10);
};

여기서 toISOString()은 YYYY-MM-DD-THH의 형태로 시간을 고쳐줍니다.

(T는 그냥 알파벳이라 무시해도 됩니다.)

여기서 slice를 했으니까 YYYY-MM-DD까지만 보일 것입니다.

간단하게 콘솔에 출력해서 확인해본 후, state에 적용합시다.

console.log(getStringDate(new Date()));

그런데 여기서 toISOString()이 우리나라 시간대를 쓰지 않아서 날짜가 간혹 틀리게 나올 때가 있습니다.

그래서 offset을 따로 정의하고 그 값을 뺀 후, local ISOTime을 새로 구하도록 하였습니다.

const getStringDate = (date) => {
  const tzoffset = new Date().getTimezoneOffset() * 60000;
  return new Date(Date.now() - tzoffset).toISOString().slice(0, 10);
};
console.log(getStringDate(new Date()));

이렇게 하면 나라별 시간대에 맞는 날짜가 나옵니다.

const [date, setDate] = useState(getStringDate(new Date()));

해당 함수를 state의 초기값으로 지정해줍니다.

 

2. DiaryEditor.js

앞서 New.js를 작성해보았습니다.

그런데 해당 부분들은 일기 작성페이지, 수정페이지 둘다 비슷하게 들어가기 때문에, 범용 컴포넌트로 내용을 옮길 것입니다.

New.js에서 기능을 분리해 옮기는 것을 실습해봅시다.

 

1. 기본 컴포넌트 틀 작성

const DiaryEditor = () => {
  return <div className='DiaryEditor'></div>;
};
 
export default DiaryEditor;

 

2. 외부 라이브러리 임포트 -> 함수 옮기기 -> 나머지 내용 옮기기

 

가장 다른곳으로부터 영향을 안 받는 코드부터 차례대로 옮깁시다.

먼저 임포트 부분입니다.

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 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>
);
};

가져올 때 최상위 div의 className을 바꾸는 것도 잊지 맙시다.

 

5. New 컴포넌트를 비우고, DiaryEditor를 임포트해 넣기

import DiaryEditor from '../components/DiaryEditor';
 
const New = () => {
  return (
    <div>
      <DiaryEditor />
    </div>
  );
};
 
export default New;

이런 과정으로 이루어집니다.

생각보다 단순합니다ㅎㅎ

 

3. 감정 선택 섹션

1. 감정 배열 만들기

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: '완전 나쁨',
  },
];

process.env.PUBLIC_URL은 현재 프로젝트 폴더의 경로입니다.

 

2. return 작성

<section>
  <h4>오늘의 감정</h4>
  <div className='input_box emotion_list_wrapper'>
    {emotionList.map((it) => (
      <div key={it.emotion_id}>{it.emotion_descript}</div>
    ))}
  </div>
</section>

section 태그로 섹션을 나눈 뒤 아까 만든 배열을 띄웁니다.

map 함수는 배열의 모든 항목에 대해 특정 코드를 적용해 새 배열을 생성해줍니다.

 

3. 감정 아이템을 EmotionItem.js로 분할

const EmotionItem = ({ emotion_id, emotion_img, emotion_descript }) => {
  return (
    <div className='EmotionItem'>
      <img src={emotion_img} />
      <span>{emotion_descript}</span>
    </div>
  );
};
 
export default EmotionItem;

분할 후 EmotionItem.js입니다.

현재 선택한 감정은 state로 처리할 것이므로, 감정아이템은 컴포넌트를 분할하였습니다.

이걸 DiaryEditor.js에서 불러옵시다.

<section>
  <h4>오늘의 감정</h4>
  <div className='input_box emotion_list_wrapper'>
    {emotionList.map((it) => (
      <EmotionItem key={it.emotion_id} {...it} />
    ))}
  </div>
</section>

이제 불러온 값을 그대로 spead해주기만 하면 됩니다.

 

4. CSS 작업

display: flex;
flex-direction: column;
justify-content: center;
align-items: center;

이렇게 display: flex를 한 후 flex-direction:column을 하면 span 태그가 그림 아래에 붙게 해줍니다.

.DiaryEditor .emotion_list_wrapper {
  display: grid;
  grid-template-columns: repeat(5, auto);
  gap: 2%;
}

grid-template-columns 부분은 각 아이콘을 column 5짜리 그리드로 나타내고, 아이콘 크기는 자동으로 한다는 뜻입니다.

.EmotionItem img {
  width: 50%;
  margin-bottom: 10px;
}

그래도 아이콘 크기가 커서 width로 직접 줄였습니다.

 

5. 감정 state 추가, setEmotion을 사용 시 쓸 함수 만들어 적용

const [emotion, setEmotion] = useState(3); 
const handleClickEmote = (emotion) => {
  return setEmotion(emotion);
};
<EmotionItem
  key={it.emotion_id}
  {...it}
  onClick={handleClickEmote}
/>

여기서 state의 초기값은 emotion id로 하였고, 임시로 초기값을 3으로 했습니다.

여기서 전달받을 emotion은 EmotionItem.js에서 갖다줘야 합니다.

 

6. EmotionItem.js 수정

const EmotionItem = ({
  emotion_id,
  emotion_img,
  emotion_descript,
  onClick,
}) => {
  return (
    <div onClick={() => onClick(emotion_id)} className='EmotionItem'>
      <img src={emotion_img} />
      <span>{emotion_descript}</span>
    </div>
  );
};
 
export default EmotionItem;

추가로 onClick을 prop으로 받고, 클릭 시 handleClickEmote 실행할 때 emotion_id를 전달받도록 return을 수정했습니다.

 

7. 감정아이템 선택됐을 때 표시하기

7-1. prop으로 전달

<EmotionItem
  key={it.emotion_id}
  {...it}
  onClick={handleClickEmote}
  isSelected={it.emotion_id === emotion}
/>

이렇게 isSelected 태그를 새로 만들어서, 현재 선택된 id와 emotion이 같은지를 반환시킵니다.

7-2. EmotionItem에서 isSelected 반영시키기

const EmotionItem = ({
  emotion_id,
  emotion_img,
  emotion_descript,
  onClick,
  isSelected,
}) => {
  return (
    <div
      onClick={() => onClick(emotion_id)}
      className={[
        'EmotionItem',
        isSelected ? `EmotionItem_on_${emotion_id}` : 'EmotionItem_off',
      ].join(' ')}>
      <img src={emotion_img} />
      <span>{emotion_descript}</span>
    </div>
  );
};
export default EmotionItem;

 

prop으로 isSelected를 받고, className에다 isSelected가 true일 경우 클래스명에 id를 붙이도록 했습니다.

각 버튼마다 선택됐을 시 background 색이 달라서, Id마다 각각 선택됐을 때 css를 따로 지정했습니다.

 

4. 오늘의 일기 작성 섹션

일기 작성 부분입니다.

1. DiaryEditor return에 section 추가

<section>
  <h4>오늘의 일기</h4>
  <div className='input_box text_wrapper'>
    <textarea />
  </div>
</section>

2. 일기 내용 state 만들고, 작성 안했을 시 focus를 주도록 useRef도 쓰기

const contentsRef = useRef();
const [contents, setContents] = useState('');
<section>
  <h4>오늘의 일기</h4>
  <div className='input_box text_wrapper'>
    <textarea
      placeholder='무슨 일이 일어나고 있나요?'
      ref={contentsRef}
      value={contents}
      onChange={(e) => setContents(e.target.value)}
    />
  </div>
</section>

return에 ref와 value, onChange까지 추가해주면 됩니다.

5. 업로드

1. return에 section 추가

<section>
  <div className='control_box'>
    <MyButton text={'취소하기'} onClick={() => navigate(-1)} />
    <MyButton text={'작성완료'} type={'positive'} onClick={() => {}} />
  </div>
</section>

2. handleSubmit 함수 정의

2-1. focus 추가

const handleSubmit = () => {
  if (contents.length < 1) {
    contentsRef.current.focus();
    return;
  }
};

2-2. useContext로 app.js에서 만들었던 onCreate 함수 불러와 사용

import { DiaryDispatchContext } from '../App';
const {onCreate} = useContext(DiaryDispatchContext);
const handleSubmit = () => {
  if (contents.length < 1) {
    contentsRef.current.focus();
    return;
  }
 
  onCreate(date, contents, emotion);
  navigate('/', { replace: true });
};

onCreate를 이용해 새 일기 객체를 생성하고, '/' 페이지로 돌아가게 합니다.

여기서 replace: true는 돌아간 페이지에서 뒤로가기를 해도 다시 작성창으로 돌아오지 않도록 하는 것입니다.

 

2-3. 완료 버튼 onClick에 handleSubmit 삽입

<MyButton
  text={'작성완료'}
  type={'positive'}
  onClick={handleSubmit}
/>

 

 

728x90

'Udemy' 카테고리의 다른 글

[React] 프로젝트 최적화  (1) 2023.05.27
[React] 일기장 프로젝트 2. 페이지 구현 - 수정, 상세화면  (0) 2023.05.26
[React] 일기장 프로젝트 - 후기  (1) 2023.04.30
[React] 일기장 프로젝트 2. 페이지 구현 - 홈화면  (0) 2023.04.25
[React] 일기장 프로젝트 1. 기초 작업  (0) 2023.04.25
  • 1. New.js 작성
  • 2. DiaryEditor.js
  • 3. 감정 선택 섹션
  • 4. 오늘의 일기 작성 섹션
  • 5. 업로드
밍비
밍비

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.