React 감정 일기장 만들기 3편 - State로 데이터 관리하기
1-2편에서 프로젝트를 만들고 페이지를 연결했어요. 이제 가장 중요한 부분이 나왔어요!
사용자가 일기를 쓸 때 날짜를 선택하고, 감정을 고르고, 내용을 입력하잖아요? 이런 데이터를 어떻게 저장하고 관리할까요? 바로 State를 사용해요!
오늘은 React의 핵심 개념인 State를 배우면서 실제로 일기를 작성하는 Editor 컴포넌트를 만들어봅시다.
이번 편에서 배울 내용
- ✅ State가 뭔지, 왜 필요한지 이해하기
- ✅ useState 훅 사용법
- ✅ 데이터 흐름 파악하기 (입력 → 이벤트 → State 업데이트)
- ✅ 날짜, 감정, 내용 입력 구현하기
- ✅ Spread Operator (…state) 활용하기
- ✅ 불변성 유지의 중요성 이해하기
State는 뭐고 왜 쓰는 걸까?
State를 쉽게 설명하면 컴포넌트가 기억하고 있는 데이터예요.
식당에서 주문서에 메뉴를 적듯이, Editor 컴포넌트도 사용자가 입력한 내용을 어딘가에 적어둬야 하잖아요? 그게 바로 State예요.
const [state, setState] = useState({
date: "2024-12-07", // 오늘 날짜
emotionId: 3, // 감정 (1~5)
content: "" // 일기 내용
});
이 주문서(State)에는 세 가지 정보가 있어요:
- date: 일기 쓰는 날짜
- emotionId: 오늘 기분 (1번부터 5번까지)
- content: 일기 내용
데이터가 흐르는 과정
사용자가 뭔가를 입력하면 어떤 일이 벌어질까요?
graph LR
A["사용자 입력"] --> B["이벤트 발생"]
B --> C["핸들러 함수 실행"]
C --> D["setState 호출"]
D --> E["State 업데이트"]
E --> F["화면 다시 그리기"]
style A fill:#ffe1e1
style D fill:#e1ffe1
style F fill:#e1f5ff
신호등 시스템이랑 비슷해요:
- 버튼을 누르면 (사용자 입력)
- “버튼 눌렸다!” 신호가 가고 (이벤트 발생)
- 정해진 동작을 실행하고 (핸들러 함수)
- 기록을 업데이트하고 (setState)
- 바뀐 내용으로 화면을 새로 그려요 (리렌더링)
Editor 컴포넌트 만들기
이제 본격적으로 Editor 컴포넌트를 만들어볼게요!
1. 파일 생성하기
터미널에서:
# components 폴더 생성
mkdir src/components
# Editor 컴포넌트 파일 생성 (윈도우는 touch 대신 echo. > 사용)
touch src/components/Editor.js
touch src/components/Editor.css
2. Editor.js 기본 구조
src/components/Editor.js 파일을 만들고 이렇게 작성하세요:
import { useState } from 'react';
import './Editor.css';
const Editor = () => {
// State 선언
const [state, setState] = useState({
date: new Date().toISOString().split('T')[0], // 오늘 날짜
emotionId: 3, // 기본 감정
content: '' // 빈 내용
});
return (
<div className="Editor">
<h2>오늘의 일기</h2>
<div>{state.date}</div>
<div>감정: {state.emotionId}</div>
<div>내용: {state.content}</div>
</div>
);
};
export default Editor;
new Date().toISOString().split('T')[0]이 복잡해 보이죠? 이건 오늘 날짜를 “2024-12-07” 형식으로 만드는 코드예요.
3. Home.js에서 Editor 사용하기
src/page/Home.js를 열어서 Editor를 추가하세요:
import Editor from '../components/Editor';
const Home = () => {
return (
<div>
<h1>Home 페이지</h1>
<Editor />
</div>
);
};
export default Home;
브라우저에서 / 경로로 가면 Editor가 보일 거예요!
날짜 입력 구현하기
이제 State를 수정하는 방법을 배워볼게요. 먼저 날짜부터!
Editor.js 업데이트
import { useState } from 'react';
import './Editor.css';
const Editor = () => {
const [state, setState] = useState({
date: new Date().toISOString().split('T')[0],
emotionId: 3,
content: ''
});
// 날짜 변경 핸들러
const handleChangeDate = (e) => {
setState({
...state, // 기존 내용 복사
date: e.target.value // 날짜만 새 값으로
});
};
return (
<div className="Editor">
<h2>오늘의 일기</h2>
{/* 날짜 선택 */}
<div>
<label>날짜: </label>
<input
type="date"
value={state.date}
onChange={handleChangeDate}
/>
</div>
<div>현재 State: {JSON.stringify(state)}</div>
</div>
);
};
export default Editor;
코드 설명
Input 부분:
<input
type="date"
value={state.date} // State의 값을 표시
onChange={handleChangeDate} // 값이 바뀌면 이 함수 실행
/>
핸들러 함수:
const handleChangeDate = (e) => {
setState({
...state, // 기존 state 복사
date: e.target.value // date만 새 값으로 교체
});
};
…state가 뭔가요?
이게 진짜 중요해요! ...state는 “기존에 있던 거 다 가져와”라는 뜻이에요.
만약 이걸 안 쓰면?
// ❌ 이렇게 하면 안 돼요!
setState({
date: e.target.value
});
// emotionId랑 content가 사라져버림!
날짜만 바꾸려다가 감정이랑 내용이 증발해버려요. 그래서 꼭 ...state로 기존 값을 복사한 다음에 바꿀 것만 바꿔야 해요.
정확히는 이렇게 동작해요:
setState({
...state, // { date: "2024-12-07", emotionId: 3, content: "" }
date: "2024-12-08" // date만 덮어씀
});
// 결과: { date: "2024-12-08", emotionId: 3, content: "" }
브라우저에서 날짜를 선택해보세요. 아래 “현재 State” 부분이 실시간으로 바뀌는 게 보일 거예요!
감정 선택 구현하기
이제 1~5 중에서 감정을 선택하는 기능을 만들어볼게요.
emotionList 만들기
일단 감정 목록 데이터를 만들어야 해요:
import { useState } from 'react';
import './Editor.css';
// 감정 목록 데이터
const emotionList = [
{ id: 1, name: "완전 좋음" },
{ id: 2, name: "좋음" },
{ id: 3, name: "그럭저럭" },
{ id: 4, name: "나쁨" },
{ id: 5, name: "끔찍함" }
];
const Editor = () => {
const [state, setState] = useState({
date: new Date().toISOString().split('T')[0],
emotionId: 3,
content: ''
});
const handleChangeDate = (e) => {
setState({
...state,
date: e.target.value
});
};
// 감정 변경 핸들러
const handleChangeEmotion = (emotionId) => {
setState({
...state,
emotionId // emotionId: emotionId 의 축약형
});
};
return (
<div className="Editor">
<h2>오늘의 일기</h2>
{/* 날짜 선택 */}
<div style={{ marginBottom: '20px' }}>
<label>날짜: </label>
<input
type="date"
value={state.date}
onChange={handleChangeDate}
/>
</div>
{/* 감정 선택 */}
<div style={{ marginBottom: '20px' }}>
<label>오늘의 감정: </label>
<div>
{emotionList.map((emotion) => (
<button
key={emotion.id}
onClick={() => handleChangeEmotion(emotion.id)}
style={{
margin: '5px',
padding: '10px 20px',
backgroundColor: state.emotionId === emotion.id ? '#64c964' : '#ececec',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
{emotion.name}
</button>
))}
</div>
</div>
<div>현재 State: {JSON.stringify(state)}</div>
</div>
);
};
export default Editor;
코드 설명
감정 버튼 렌더링:
{emotionList.map((emotion) => (
<button
key={emotion.id}
onClick={() => handleChangeEmotion(emotion.id)}
...
map()으로 emotionList 배열을 돌면서 버튼 5개를 만들어요.
클릭 이벤트:
onClick={() => handleChangeEmotion(emotion.id)}
버튼을 클릭하면 그 감정의 id를 handleChangeEmotion 함수에 전달해요.
조건부 스타일링:
backgroundColor: state.emotionId === emotion.id ? '#64c964' : '#ececec'
선택된 감정은 초록색, 나머지는 회색으로 표시해요.
버튼 클릭 흐름
sequenceDiagram
participant 사용자
participant 버튼
participant Editor
participant State
사용자->>버튼: "좋음" 클릭
버튼->>Editor: handleChangeEmotion(2) 호출
Editor->>State: setState({...state, emotionId: 2})
State->>Editor: State 업데이트 완료
Editor->>버튼: 다시 렌더링 (2번 버튼 초록색)
버튼->>사용자: 시각적 피드백 표시
브라우저에서 감정 버튼들을 클릭해보세요. 선택한 버튼이 초록색으로 바뀌고, 아래 State 값도 바뀌는 게 보일 거예요!
일기 내용 입력하기
마지막으로 일기 내용을 입력받는 textarea를 추가해볼게요:
const Editor = () => {
const [state, setState] = useState({
date: new Date().toISOString().split('T')[0],
emotionId: 3,
content: ''
});
const handleChangeDate = (e) => {
setState({
...state,
date: e.target.value
});
};
const handleChangeEmotion = (emotionId) => {
setState({
...state,
emotionId
});
};
// 내용 변경 핸들러
const handleChangeContent = (e) => {
setState({
...state,
content: e.target.value
});
};
return (
<div className="Editor">
<h2>오늘의 일기</h2>
{/* 날짜 선택 */}
<div style={{ marginBottom: '20px' }}>
<label>날짜: </label>
<input
type="date"
value={state.date}
onChange={handleChangeDate}
/>
</div>
{/* 감정 선택 */}
<div style={{ marginBottom: '20px' }}>
<label>오늘의 감정: </label>
<div>
{emotionList.map((emotion) => (
<button
key={emotion.id}
onClick={() => handleChangeEmotion(emotion.id)}
style={{
margin: '5px',
padding: '10px 20px',
backgroundColor: state.emotionId === emotion.id ? '#64c964' : '#ececec',
border: 'none',
borderRadius: '5px',
cursor: 'pointer'
}}
>
{emotion.name}
</button>
))}
</div>
</div>
{/* 일기 내용 */}
<div style={{ marginBottom: '20px' }}>
<label>오늘의 일기: </label>
<textarea
value={state.content}
onChange={handleChangeContent}
placeholder="오늘은 어땠나요?"
style={{
width: '100%',
minHeight: '200px',
padding: '10px',
fontSize: '16px',
borderRadius: '5px',
border: '1px solid #ccc'
}}
/>
</div>
<div>현재 State: {JSON.stringify(state)}</div>
</div>
);
};
Textarea 특징
<textarea
value={state.content}
onChange={handleChangeContent}
placeholder="오늘은 어땠나요?"
/>
value={state.content}: State의 값을 textarea에 표시onChange={handleChangeContent}: 값이 바뀔 때마다 State 업데이트placeholder: 아무것도 입력 안 했을 때 보이는 힌트 텍스트
키보드로 뭔가를 입력할 때마다 handleChangeContent가 실행되고, state.content가 계속 업데이트돼요. 그래서 입력하는 글자가 바로바로 화면에 보이는 거예요!
작성 완료 버튼 추가하기
이제 “작성 완료” 버튼을 만들어서 최종 데이터를 확인해볼게요:
const Editor = () => {
// ... (위의 코드 동일)
// 작성 완료 핸들러
const handleSubmit = () => {
// 나중에는 서버로 전송하거나 Context에 저장
// 지금은 alert로 확인
alert(JSON.stringify(state, null, 2));
};
return (
<div className="Editor">
{/* ... (위의 코드 동일) */}
{/* 작성 완료 버튼 */}
<button
onClick={handleSubmit}
style={{
padding: '15px 30px',
fontSize: '18px',
backgroundColor: '#64c964',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
marginTop: '20px'
}}
>
작성 완료
</button>
<div style={{ marginTop: '20px' }}>
현재 State: {JSON.stringify(state, null, 2)}
</div>
</div>
);
};
브라우저에서 날짜, 감정, 내용을 입력하고 “작성 완료”를 눌러보세요. Alert 창에 전체 데이터가 나타날 거예요!
확인하기
여기까지 잘 따라오셨나요? 다음 항목들을 확인해보세요:
✅ 파일 생성 확인
src/components/Editor.js파일 생성됨src/page/Home.js에서 Editor 컴포넌트 사용 중
✅ State 동작 확인 (React DevTools 설치 권장)
- 날짜 선택 시 state.date 업데이트
- 감정 버튼 클릭 시 state.emotionId 변경 및 버튼 색 변경
- 내용 입력 시 state.content 실시간 업데이트
- “작성 완료” 버튼 클릭 시 전체 state 출력
✅ React DevTools로 확인하기
- Chrome 확장 프로그램에서 React DevTools 설치
- 컴포넌트 탭에서 Editor 선택
- Hooks → State 섹션에서 값 변화 실시간 확인
자주 묻는 질문
Q1. State는 어디에 저장되나요?
컴포넌트 안의 메모리에 저장돼요. 브라우저를 새로고침하면 사라지고, 페이지를 닫으면 당연히 사라져요. 영구 저장하려면 데이터베이스나 localStorage를 써야 해요.
Q2. 왜 꼭 …state를 써야 하나요?
React는 State가 “진짜 바뀌었는지” 확인하는 방법이 좀 특이해요. 내용물을 하나하나 비교하는 게 아니라, “이 상자가 새 상자인지 기존 상자인지”를 봐요.
// ❌ 이렇게 하면 React가 변화를 감지 못할 수 있어요
state.date = "2024-12-08"; // 같은 상자, 내용만 바꿈
// ✅ 이렇게 해야 확실해요
setState({...state, date: "2024-12-08"}); // 새 상자!
Q3. useState 여러 개로 나누면 안 되나요?
물론 돼요! 이렇게도 할 수 있어요:
const [date, setDate] = useState(new Date().toISOString().split('T')[0]);
const [emotionId, setEmotionId] = useState(3);
const [content, setContent] = useState('');
관련된 데이터를 묶어두는 게 관리하기 편할 때 객체로 쓰는 거예요.
Q4. onChange는 언제 실행되나요?
입력값이 바뀔 때마다 즉시 실행돼요:
- input: 값이 변경될 때마다
- textarea: 글자를 칠 때마다
- select: 옵션을 선택할 때
엄청 자주 실행되니까 무거운 작업은 넣지 마세요!
정리하며
오늘 3편에서 배운 State 관리를 한눈에 정리하면:
핵심만 기억하세요:
- 📦 State는 컴포넌트가 기억하는 데이터
- 🔄 setState로만 State를 변경해야 화면이 업데이트됨
- 📋 …state로 기존 값을 복사해야 다른 값이 안 사라짐
- ⚡ onChange로 사용자 입력을 즉시 State에 반영
다음 편에서는 Editor를 더 깔끔하게 만들기 위해 재사용 가능한 컴포넌트들을 만들어볼 거예요. EmotionItem, Button, Header 같은 작은 부품들로 나눠서 코드를 정리하는 방법을 배웁니다!
시리즈 네비게이션
- 1편: React 프로젝트 시작하기 - 기본 구조 이해
- 2편: React Router로 페이지 만들기
- 3편: State로 데이터 관리하기 ← 현재 글
- 4편: 재사용 가능한 컴포넌트 만들기 (다음 편)
- 5편: 전체 앱 연결 및 데이터 관리