[React] 45. New 페이지 기능 구현

서회정's avatar
Mar 20, 2026
[React] 45. New 페이지 기능 구현

 
새로운 일기를 작성하는 New 페이지의 기능을 구현해보자.
 

1. Header

 
뒤로가기 버튼의 기능을 구현해보자.
 
리액트 라우터 돔에서 제공하는 useNavigate 라는
커스텀 훅을 사용하면 되는데, 먼저 컴포넌트 내부에서
함수를 호출하고 변수에 담아주자.
 
그리고 클릭이벤트 자리에 콜백함수로 감싸
호출하기만 하면 되는데,
인수로 -1이라는 숫자를 넘겨주면 된다.
 
import Button from "../components/Button"; import Header from "../components/Header"; import Editor from "../components/Editor"; import { useNavigate } from "react-router-dom"; const New = () => { const nav = useNavigate(); return ( <div> <Header title={" 일기 쓰기"} leftChild={<Button text={"< 뒤로가기"} onClick={() => nav(-1)} />} /> <Editor /> </div> ); }; export default New;
 

2. Editor 요소 조작 상태 관리

 
사용자가 입력한 값을 화면에 렌더링하고,
상태로 관리할 수 있도록 하자.
 
최종적으로는 새로운 글을 작성해서 리스트로 넘기는 것 까지이다.
 

1. 작성 날짜 상태 관리

 
일기를 작성하는 날짜를 사용자가 선택했을 때
화면에 렌더링하고, 상태로 저장될 수 있도록 하자.
 

notion image

 

1. 새로운 상태 생성 및 초기화

 
useState 훅을 불러와 새로운 상태를 만들어주자.
초기 값으로는 작성날짜 뿐만 아니라 emotionId와
입력 내용이 들어갈 content도 만들어주어야한다.
 
따라서 객체 형태로 만들어 각각의 초기값을 넣는데
작성 날짜의 초기 값은 현재 날짜로 하기 위해 새로운
날짜 객체를 넣어주었다.
 
이모션아이디는 중립값인 3, content는 빈 문자열로
넣어 placeholder 값이 노출되도록 하자.
 
const [input, setInput] = useState({ createdDate: new Date(), emotionId: 3, content: "", });
 
초기값을 세팅해주기 위해 해당 input
만들어둔 상태의 초기값을 넣어주자.
 
<section className="date_section"> <h4>오늘의 날짜</h4> <input name="createdDate" value={input.createdDate} type="date" /> </section>;
 

2. Date 객체 문자열로 변경하기

 
하지만 브라우저를 새로고침해도 초기값이
해당 input 안에 세팅되지 않는다.
 
이러한 현상은 input이라는 태그의 value 속성이
Date 객체를 인식하지 못하며, 오로지 문자열로 입력된
값을 받기 때문이다.
 
따라서 우리는 Date 객체를 문자열로 바꾸는 함수를 만들고
이 함수 안에 기존의 초기값을 인수로 넣어주어야한다.
 
const getStringedDate = (targetDate) => { // 날짜 -> YYYY.MM.DD let year = targetDate.getFullYear(); let month = targetDate.getMonth() + 1; let date = targetDate.getDate(); };
 
Date객체를 YYYY-MM-DD 형태로 만들기 위해
년, 월, 일을 각각 변수로 만들어 만들어둔 함수의
매개변수로 받아온 Date 객체에서 추출해서 할당하자.
 
참고로 자바스크립트에서 월은 0부터 시작하기 때문에
이번 달을 렌더링하기 위해 +1 해주어야한다.
 
그리고 한 가지 더 필요한 작업이 있다.
월이나 일이 한 자리 수로 들어오는 경우에는
앞에 0을 붙여 두 자리 수로 만들어주어야한다.
 
조건문을 통해 이 둘의 숫자가 10보다 작을 때
리터럴 문법을 사용하여 앞에 0을 붙여서 재할당해주자.
 
const getStringedDate = (targetDate) => { // 날짜 -> YYYY.MM.DD let year = targetDate.getFullYear(); let month = targetDate.getMonth() + 1; let date = targetDate.getDate(); // 월이나 일이 한자리 수일 때 두자리로 바꿔주기 if (month < 10) { month = `0${month}`; } if (date < 10) { date = `0${date}`; } };
마지막으로 우리가 원하는 폼의 문자열로
이를 반환하기만 하면 된다.
 
const getStringedDate = (targetDate) => { // 날짜 -> YYYY.MM.DD let year = targetDate.getFullYear(); let month = targetDate.getMonth() + 1; let date = targetDate.getDate(); // 월이나 일이 한자리 수일 때 두자리로 바꿔주기 if (month < 10) { month = `0${month}`; } if (date < 10) { date = `0${date}`; } return `${year}-${month}-${date}` };
 
이제 inputvalue속성에 해당 함수를 호출해서
인수로 기존의 초기값을 넣어주면 화면에 초기값이
잘 반영되어 들어간걸 확인할 수 있다.
 
<section className="date_section"> <h4>오늘의 날짜</h4> <input name="createdDate" value={getStringedDate(input.createdDate)} type="date" /> </section>;
 

3. 이벤트 핸들러 만들기

 
이제 input의 값이 변경될 때 실행되는
이벤트 핸들러를 만들어보자.
 
먼저 합성이벤트객체를 매개변수로 받기 위해
e로 받아주고 어떤 요소에 입력이 들어왔고,
또 입력된 값이 무엇인지 확인하기 위해 콘솔에
출력하여 확인해보도록 하자.
 
const onChangeInput = (e) => { console.log(e.target.name); // 어떤 요소에 입력이 들어온건지 console.log(e.target.value); // 입력된 값은 무엇인지 };
 
input에 해당하는 함수를 onChange이벤트로 넣어주고
name속성을 createdDate로 설정해주고 확인해야한다.
 
<section className="date_section"> <h4>오늘의 날짜</h4> <input name="createdDate" onChange={onChangeInput} value={getStringedDate(input.createdDate)} type="date" /> </section>;
 
날짜를 입력하는 폼을 조작해보면 콘솔에
다음 이미지와 같은 값이 찍힌다.
 
notion image
 

⚠️ 타입 에러 발생 원인?! ⚠️

 
하지만 우리는 createdDate 상태를 Date객체로 관리하고 있기 때문에
이렇게 문자열로 값이 들어가지 않도록 해야한다.
 
notion image
 
억지로 넣어보고 안되는 이유도 같이 확인해보자.
 
상태를 변경하는 setInput 함수를 호출하여 인수자리에
객체형태로 먼저 현재 상태를 전개연산자로 뿌리고
사용자에 의해 조작된 요소의 값을 변경해보자.
 
const onChangeInput = (e) => { setInput({ ...input, [e.target.name]: e.target.value, }); };
 
그럼 이런 타입에러 메세지를 확인할 수 있다.
targetDate.getFullYear을 사용할 수 없다는 것이다.
 
notion image
 
우리가 먼저 작성해둔 Date객체를 문자열로 변환하는
로직에서 getFullYeargetMonth같은 메서드를 사용하는데
Date객체가 아닌 문자열 타입에서는 이러한 메서드를 사용하지
못하기 때문이다.
 
따라서 상태로 저장할 때 문자열로 받으면 전체 로직이 꼬여서
이런 타입 에러로 알려주는 것이다!
 
notion image
 
그럼 우리는 해당 함수 내부에서 문자열로 들어온 값을
다시 Date 객체로 바꾸는 로직을 추가해야한다.
 
먼저 namevalue라는 변수에 각각의 이벤트 객체
내부의 값을 할당해주자.
 
그리고 조건문을 통해 namecreatedDate일 때
새로운 Date객체를 만들어 인수로 문자열인 value
넣고 다시 value에 재할당 해주도록 하자.
 
그런 다음 값이 할당된 변수 이름으로 바꾸어
setInput 함수 내부에서 값이 변경될 수 있도록
수정해주면 된다.
 
const onChangeInput = (e) => { let name = e.target.name; let value = e.target.value; if (name === "createdDate") { value = new Date(value); } setInput({ ...input, [name]: value, }); };
 

2. 이모션 아이디 상태 관리

 
다음으로는 오늘의 감정을 선택하는 폼의
상태 관리 기능을 만들어보자.
 
오늘의 감정 선택 폼은 기본으로 제공되는
input과 같은 조작 요소가 아닌 일반 컴포넌트다.
 
따라서 직접 이벤트 객체를 만들어 주어야한다.
 

notion image

 
이모션 아이템 컴포넌트에 onClick이라는
props를 만들어 콜백함수를 전달해주자.
 
콜백 함수 내부에서는 onChangeInput이라는
상태 변경 함수를 호출하고 객체 형태로 직접
targetnamevalue를 만들어 값을 전달한다.
 
<section className="emotion_section"> <h4>오늘의 감정</h4> <div className="emotion_list_wrapper"> {emotionList.map((item) => ( <EmotionItem onClick={() => onChangeInput({ target: { name: "emotionId", value: item.emotionId, }, }) } key={item.emotionId} {...item} isSelected={item.emotionId === emotionId} /> ))} </div> </section>;
 
다음으로 이모션아이템 컴포넌트로 가서 props를 받고
조작이 일어나는 요소인 최상위 divonClick 이벤트로
값을 전달해주자.
 
notion image
 
이제 리액트 개발자 도구를 켜서 Editor 컴포넌트의
hook을 확인하면 조작하는대로 emotionId
변경되고 있는 모습을 확인할 수 있다.
 
 
notion image
 
하지만 어째서인지 화면이 바뀌지 않는다…?
 
임시로 넣어둔 상수값때문이다.
 
const emotionId = 1; 이 코드를 삭제하고
isSelected props로 전달되는 비교식을
input.emotionId로 바꾸어주면 조작하는대로
화면에 렌더링도 문제없이 된다!
 
notion image
 

3. content 상태 관리

 
일기 내용 작성을 받는 content 상태 관리는 어렵지 않다.
이미 상태 변경 함수와 이벤트 핸들러를
모두 만들었기 때문에 별도로 추가할 로직은 없다.
 
namevalue 속성을 추가해주고
onChange 이벤트에 onChangeInput 함수가
호출될 수 있도록만 해주자.
 
<section className="content_section"> <h4>오늘의 일기</h4> <textarea onChange={onChangeInput} value={input.content} name="content" placeholder="오늘은 어땠나요?" ></textarea> </section>;
 
notion image
 

3. 새로운 일기 추가 기능

 
Editor컴포넌트에서 상태로 관리되는 내용을
작성 완료 버튼을 눌러 새로운 일기 데이터에
추가할 수 있게 하자.
 

1. 새로운 일기 데이터 추가

 
우리는 앞서 App컴포넌트 내부에 새로운 일기를
추가하는 함수를 만들어두었고, 모든 하위 컴포넌트에서
공급받아 사용할 수 있도록 Provider 컴포넌트로
전체를 감싸놓았다.
 
그래서 새로운 일기를 추가하는 함수를 호출하여
사용하기만 하면 된다.
 
notion image
 
하지만 우리가 일기를 작성하는 페이지에서
불러와 사용하고 있는 Editor 컴포넌트는
새로운 일기 작성뿐만 아니라 수정하는 페이지에서도
사용하는 공통 컴포넌트이다.
 
따라서 제출하는 버튼의 클릭 이벤트에 컨텍스트로부터
바로 해당 함수를 공급받아 호출한다면 위험하다.
 
그래서 Editor컴포넌트의 상위 컴포넌트에서 이 동작을
props로 받아 실행하기만 하도록 해야 한다.
 
그리고 어떤 페이지에서 이 컴포넌트에 접근하는지에 따라
상위 컴포넌트에서 결정하여 적절한 함수를 서로 다르게
호출하기만하면 된다.
 

 
먼저 Editor컴포넌트에서 onSubmit이라는 props를 받자.
 
notion image
 
그리고 해당 컴포넌트 내부에 이벤트 핸들러를 만들어
props로 전달받은 함수에 인수로 이 컴포넌트에서
데이터로 관리되고 있는 상태인 input을 넘겨주자.
 
const onClickSubmitButton = () => { onSubmit(input); };
 
다음으로 작성 완료 버튼에 onClick 속성으로
만들어둔 이벤트 핸들러를 호출하자.
 
<section className="button_section"> <Button text={"취소하기"} /> <Button onClick={onClickSubmitButton} text={"작성 완료"} type={"POSITIVE"} /> </section>;
 
이제 새로운 일기를 작성하는 페이지인
New컴포넌트로 가서 onCreate 함수를 공급받기 위해
Context훅과 공급받을 Context를 불러와주자.
 
notion image
 
그리고 컴포넌트 내부에서 useContext 함수를 호출하여
공급을 받을 DiaryDispatchContext를 인수로 넣어
onCreate 함수를 구조분해할당 문법으로 받아주자.
 
const { onCreate } = useContext(DiaryDispatchContext);
 
그리고 props로 전달해줄 onSubmit 함수를 만들자.
매개변수로 input을 받고, onCreate 함수를 호출해
해당 함수의 매개변수로 전달되는 각각의 데이터에 맞게
인수로 넣어주자.
 
const onSubmit = (input) => { onCreate(input.createdDate, input.emotionId, input.content); };
 
다음으로 Editor컴포넌트에 props로 이 함수를 넘겨주자.
 
<div> <Header title={" 일기 쓰기"} leftChild={<Button text={"< 뒤로가기"} onClick={() => nav(-1)} />} /> <Editor onSubmit={onSubmit} /> </div>;
 
일기를 작성해서 제출하기 버튼을 누르고
뒤로가기를 누르면 추가된 일기를 볼 수 있다.
 
notion image
 
하지만 개발자 도구에서 보면 기존에 있던
일기데이터와 다른 부분이 보인다.
 
새로 만들어진 일기는 작성시간이 Date 객체로 들어가고
기존에 있던 일기는 타임스탬프로 저장되어 있다.
 
타임스탬프로 통일하자!
 
notion image
 
getTime 메서드를 추가하기만 하면 해결된다!
 
const onSubmit = (input) => { onCreate(input.createdDate.getTime(), input.emotionId, input.content); };
 
이제 새로 추가한 일기도
타임스탬프 형식으로 저장된다!
 
notion image
 

2. 제출 후 Home으로 이동 / 뒤로가기 방지

 
제출 버튼을 눌렀을 때 페이지가 이동하지 않으면
사용자는 일기가 제대로 추가되었는지 알 수 없다.
 
버튼을 누르면 일기가 추가되고 홈 화면으로
이동할 수 있게 하고, 그와 함께 뒤로가기를 하지
못하도록 같이 처리해보자.
 

 
const onSubmit = (input) => { onCreate(input.createdDate.getTime(), input.emotionId, input.content); nav("/", { replace: true }); };
 
New페이지에서는 이미 사용중인 nav가 만들어져 있다.
이를 호출하여 사용하기만 하면 된다.
 
onCreate 함수 실행 후에 nav함수를 호출해
첫 번째 인수로는 이동하고싶은 주소를 넣자.
 
두 번째 인수로 넣는 것이 뒤로가기를 방지해주는
코드를 넣을 수 있는 자리다.
 
객체 형태로 replace : true 를 넣어주자.
 
const onSubmit = (input) => { onCreate(input.createdDate.getTime(), input.emotionId, input.content); nav("/", { replace: true }); };
 

3. 취소하기 버튼

 
우선 Editor컴포넌트에 useNavigate 커스텀 훅을 추가하자.
 
notion image
 
useNavigate 함수를 호출하여 컴포넌트 내부에서 사용할 수 있도록 하자.
 
const nav = useNavigate();
 
취소하기 버튼에 onClick이벤트로 해당 함수를
호출하여 뒤로가는 -1을 인수로 넣어주면 끝이다.
 
<section className="button_section"> <Button onClick={() => nav(-1)} text={"취소하기"} /> <Button onClick={onClickSubmitButton} text={"작성 완료"} type={"POSITIVE"} /> </section>;
Share article

clubnerdy