감정 일기장의 주된 기능은 일기 상태를 관리하는 것이다.
CRUD 기능을 차례대로 구현해보자.
1. 일기 작성하기
먼저 새로운 일기를 추가해주는 기능을 만들어야한다.
일기라는 상태를 사용하는 모든 컴포넌트의 공통 상위
컴포넌트는
App 컴포넌트이다.따라서
App 컴포넌트에서 일치를 추가하는 함수를 만들고New 컴포넌트에서 일기를 추가하면 App 컴포넌트에서해당 함수가 호출될 수 있도록 해야 한다.
먼저 실질적으로 일기를 추가하는
onCreate 함수를 만들자.매개변수로는
createdDate, emotionId, content를 받는다.const onCreate = (createdDate, emotionId, content) => {};이 함수는
dispatch 함수를 호출하는데,디스패치가 실행될 때 함께 넘겨줄
action 객체를넣어주자.
const onCreate = (createdDate, emotionId, content) => {
dispatch({});
};action 객체 안에는 요청 형태를 담는 type과data 프로퍼티를 넣고 데이터 변경을 일으킬 상세한형태를 서술하여 넣어주자.
type에는 CREATE, data는 객체 형태로 추가될프로퍼티를 각각 넣어주자.
const onCreate = (createdDate, emotionId, content) => {
dispatch({
type: "CREATE",
data: {
createdDate,
emotionId,
content,
},
});
};일기를 추가할 때 위 세 가지 프로퍼티와 함께
기존에 있던 목업 데이터들과 겹치지 않는
id를추가 해주어야한다.
그리고 그
id는 일기가 추가될 때마다 1씩추가되어야한다.
먼저
id를 저장할 레퍼런스 객체가 필요하니useRef 훅을 추가하여 새로운 레퍼런스 객체를 만들어초기값을 목업 데이터 바로 다음 숫자로 할당해주자.
const isRef = useRef(4);그리고 아까 만들어둔
dispatch의 인수인 객체 안에data 프로퍼티 내부에 후위 연산자를 활용해 추가해주자.이제 새로 만들어지는 일기 객체는
id가 4로 시작하며새로 작성될 때마다 1씩 더해지게 될 것이다.
const onCreate = (createdDate, emotionId, content) => {
dispatch({
type: "CREATE",
data: {
id: isRef.current++,
createdDate,
emotionId,
content,
},
});
};다음으로
data를 바로 반환할 수 있게 임시로 만들어 둔reducer 함수를 수정해주자.switch문을 작성하여 action객체의 type이CREATE일 때 기존의 상태를 뿌리고 상단에action의 data가 추가해주자.function reducer(state, action) {
switch (action.type) {
case "CREATE":
return [action.data, ...state];
}
}일기를 추가하는 페이지의 UI가 아직 만들어지지 않아서
만든 기능이 잘 실행되는지 확인하기 위해 임시 버튼을 만들어주자.
일기를 생성하는 함수인
onCreate를 호출할 수 있도록버튼의
onClick이벤트에 콜백 함수로 감싸 해당 함수를 넣고인수로 현재 시간을 가져오는 타임스탬프와 감정을 나타내는
프로퍼티에 1, content 프로퍼티 자리에 hello를 넣어주자.
<button
onClick={() => {
onCreate(new Date().getTime(), 1, "hello");
}}
>화면에 렌더링 된 임시 버튼을 누르면
리액트 개발자 도구에서
onCreate의 인수로 넣어준 값들이일기 객체로 리스트에 잘 추가 되어있는 것을 확인할 수 있다.

2. 일기 수정하기
다음은 기존에 있던 일기를 수정하는 기능을 만들어보자.
먼저 일기를 수정하는 함수를 만들자.
매개변수로는
id, createdDate, emotionId, content 네 가지프로퍼티를 모두 받자.
그리고 dispatch 함수를 내부에서 호출하여
action 객체를만들어 주어야 한다.
변경되는 프로퍼티는
id를 제외한 나머지 세 가지지만 id를넣는 이유는 사용자가 수정하고자하는 타겟이 되는
id와일치하는 데이터를 비교하여 찾기 위함이다.
const onUpdate = (id, createdDate, emotionId, content) => {
dispatch({
type: "UPDATE",
data: {
id,
createdDate,
emotionId,
content,
},
});
};reducer 함수로 가서 action.type이 UPDATE인케이스를 추가하여주자.
우리가
state에 보관하고 있는 상태는 하나의 일기가 아닌여러 개의 일기 리스트이다.
따라서 상태를 변경하기 위해 기존의 배열을 수정하지 않고
새로운 배열을 만들어 반환하도록
map 메서드를 사용하자.배열을 순회하여 각
item의 id와 action.data.id가 같은지비교하고 같으면 바뀐 데이터인
action.data 로 갈아 끼워주고그렇지 않은
item은 그대로 반환한다.function reducer(state, action) {
switch (action.type) {
case "CREATE":
return [action.data, ...state];
case "UPDATE":
return state.map((item) =>
String(item.id) === String(action.data.id) ? action.data : item,
);
}
}기능 테스트를 위해 마찬가지로 임시 버튼을 만들자.
<button
onClick={() => {
onUpdate(1, new Date().getTime(), 3, "수정된 일기 내용입니다.");
}}
>
일기 수정 테스트
</button>;onUpdate의 매개변수로 전달한 값들이 잘 들어가수정이 되고있다!

3. 일기 삭제하기
기존의 일기를 삭제하는 로직도 만들어보자.
id를 비교해서 삭제만 하면 되기 때문에 매개변수로id만 받아주자.const onDelete = (id) => {
dispatch({
type: "DELETE",
id,
});
};배열을 필터링하여 각
item의 id가 action.id와 같지 않은 것만반환하여주자.
function reducer(state, action) {
switch (action.type) {
case "CREATE":
return [action.data, ...state];
case "UPDATE":
return state.map((item) =>
String(item.id) === String(action.data.id) ? action.data : item,
);
case "DELETE":
return state.filter((item) => item.id !== action.id);
}
}마찬가지로 테스트 버튼을 만들어 삭제가 잘 되는지
확인해보자.
<button
onClick={() => {
onDelete(1);
}}
>
일기 삭제 테스트
</button>;매개변수로 넣어준 1과
id가 일치하는 요소만 삭제되었다.
그 밖에 다른 요청이 있을 땐
state를 그냥 반환하도록default 설정을 해주자.function reducer(state, action) {
switch (action.type) {
case "CREATE":
return [action.data, ...state];
case "UPDATE":
return state.map((item) =>
String(item.id) === String(action.data.id) ? action.data : item,
);
case "DELETE":
return state.filter((item) => item.id !== action.id);
default:
return state;
}
}4. Centext 추가
컨텍스트를 추가하여 만들어진 모든 함수가
App 컴포넌트 하위에 있는 모든 컴포넌트에전달될 수 있도록 해주자.
리액트로부터 createContext 내장함수를 불러와주자.
import { useReducer, useRef, createContext } from "react";먼저 일기의 상태를 전달해주는 새로운 컨텍스트를 만들자.
const DiaryStateContext = createContext();그리고
Routes 컴포넌트를 감싸 Routes 하위에 있는모든 컴포넌트에서
data라는 상태를 value로 받아사용할 수 있도록 하자.
<DiaryStateContext.Provider value={data}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/new" element={<New />} />
<Route path="/diary/:id" element={<Diary />} />
<Route path="/edit/:id" element={<Edit />} />
<Route path="*" element={<Notfound />} />
</Routes>
</DiaryStateContext.Provider>;
위에서 만든 세 가지 기능인 함수들도
컨텍스트로 만들어 전달할 수 있도록 하자.
const DiaryDispatchContext = createContext();세 개의 함수를 넣어야 하므로 객체 형태로 전달한다.
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={{ onCreate, onUpdate, onDelete }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/new" element={<New />} />
<Route path="/diary/:id" element={<Diary />} />
<Route path="/edit/:id" element={<Edit />} />
<Route path="*" element={<Notfound />} />
</Routes>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>;리액트 개발자 도구에서 각 함수가 객체 형태로
전달되고 있는 상태를 확인할 수 있다.

Share article