목차 돌아가기:
binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial-v2
수정으로 들어가기 전에 Article.js 에 수정 버튼을 만들어준다.
// Article.js
const history = useHistory();
function onClickUpdateButton() {
history.push(`/update/${params?.articleId ?? 0}`);
}
<div>
<button onClick={onClickUpdateButton}>수정</button>
</div>
나는 게시판 명 위에 만들었다.
수정의 경우 "/update/:articleId" 로 이동하므로
파라미터 articleId를 이용하여 수정할 게시글 내용을 불러온다.
// articleSlice.js
setArticle: (state, action) => {},
위와 같은 액션을 만들고
// articleSaga.js
function* asyncSetArticle(action) {
try {
const response = yield call(apiGetArticle, action.payload?.articleId);
if (response?.status === 200) {
yield call(action.payload?.setArticle, response?.data ?? {});
} else {
yield alert(`불러오기 실패 Error: ${response.status}, ${response.statusText}`);
history.goBack();
}
} catch(e) {
console.error(e);
yield alert(`불러오기 실패 Error: ${e?.response?.status}, ${e?.response?.statusText}`);
history.goBack();
}
}
function* watchSetArticle() {
while(true) {
const action = yield take(articleActions.setArticle);
yield call(asyncSetArticle, action);
}
}
export default function* articleSaga()
{
yield all([fork(watchGetArticleList), fork(watchGetArticle),
fork(watchUpdateArticleViews), fork(watchPostArticle),
fork(watchSetArticle)]);
}
saga는 이렇게 작성하였다.
asyncSet~ 액션은 게시글 한 개 내용을 가져올 때 썼던 api함수를 재활용 했다. getArticle 액션을 다시 쓸 수 있음 좋았겠지만 조회수 문제 때문에 안된다. (게시글 조회수 update 편에서 아쉬워했던 이유가 이것 때문..)
게시판 내용 불러오기가 실패한 경우 알림창만 띄우고 뒤로가기 할 것이므로 Success, Fail 추가 액션은 만들지 않았다.
alert 가 떠있을 때 콘솔창을 열면 에러가 보일 순 있겠다.
저기 action.payload?.setArticle 은 액션을 통해 넘겨준 함수를 꺼내는 부분인데 call을 이용해서 호출한다.
if (response?.status === 200) {
yield call(action.payload?.setArticle, response?.data ?? {});
}
넘겨주는 부분은 /views/Post.js 에서 여기다
// Post.js
const params = useParams();
useEffect(() => {
if (params?.articleId) {
dispatch(articleActions.setArticle({ articleId: params?.articleId, setArticle }));
} else {
setArticle({}); // 새글 쓰기 염두
}
}, [dispatch, params?.articleId]);
useState로 만든 setArticle 함수를 action.payload로 바로 넘겨주는 것이다
컴파일이 된 뒤 수정 버튼을 클릭하여 렌더링된 update 화면은 다음과 같이 나타나야 한다.
이제 본격적으로 수정해보자
이젠 거의 패턴화된 순서
articleSlice 에 액션 생성
// articleSlice.js
putArticle: (state, action) => {},
putArticleSuccess: (state, action) => {},
putArticleFail: (state, action) => {
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
articleSaga에 async 함수 생성
api는 조회수 put했던 api함수를 재활용한다.
// articleSaga.js
function* asyncPutArticle(action) {
try {
const response = yield call(apiPutArticle, {
...action.payload,
updateData: Date.now()
});
if (response?.status === 200) {
yield put(articleActions.putArticleSuccess());
history.push(`/article/${response?.data?.id ?? 0}`);
} else {
yield put(articleActions.putArticleFail(response));
yield alert(`수정실패 \n Error: ${response.status}, ${response.statusText}`);
}
} catch(e) {
console.error(e);
yield put(articleActions.putArticleFail(e?.response));
yield alert(`수정실패 \n Error: ${e?.response?.status}, ${e?.response?.statusText}`);
}
}
function* watchPutArticle() {
while(true) {
const action = yield take(articleActions.putArticle);
yield call(asyncPutArticle, action);
}
}
export default function* articleSaga()
{
yield all([fork(watchGetArticleList), fork(watchGetArticle),
fork(watchUpdateArticleViews), fork(watchPostArticle),
fork(watchSetArticle), fork(watchPutArticle)]);
}
/views/Post.js 는 insert 와 update 때 같이 사용하므로 등록 버튼에서 수정과 등록을 가려줄 if문을 추가해줘야 한다.
// Post.js
function onClickSubmitButton() {
if (article?.boardId > 0 && article?.title)
{
if (article?.id > 0)
{
dispatch(articleActions.putArticle(article));
} else {
dispatch(articleActions.postArticle(article));
}
} else {
alert("게시판과 제목은 필수값입니다.");
}
}
수정과 신규등록 차이점은 게시글 id 존재 여부이므로 일단 저렇게 if문 조건을 달아본다.
수정이 잘 일어나면 된 거다
그리고 다음 과정(설정 만들기-게시판, 코드 등록,수정,삭제)을 위해 버튼 하나를 추가해두겠다.
버튼 추가하면서 리팩토링 된 부분이 있다.
// Post.js
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SELECT } from '../utils/events';
import { articleActions } from '../slices/articleSlice';
import { useHistory, useParams } from 'react-router';
function Post() {
const { boardList, boardStatus, boardStatusText } = useSelector(
(state) => ({
boardList: state.boardReducer.boardList,
boardStatus: state.boardReducer.status,
boardStatusText: state.boardReducer.statusText
}));
const { articleStatus, articleStatusText } = useSelector(
(state) => ({
articleStatus: state.articleReducer.status,
articleStatusText: state.articleReducer.statusText
}));
const [ article, setArticle ] = useState({});
const dispatch = useDispatch();
const history = useHistory();
const params = useParams();
function onChangeArticle(e) {
setArticle({
...article,
[e.currentTarget.name]: e.currentTarget.value
});
}
function onClickSubmitButton() {
if (article?.boardId > 0 && article?.title)
{
if (article?.id > 0)
{
dispatch(articleActions.putArticle(article));
} else {
dispatch(articleActions.postArticle(article));
}
} else {
alert("게시판과 제목은 필수값입니다.");
}
}
function onClickMoveToControlButton() {
history.push("/control");
}
useEffect(() => {
if (params?.articleId) {
dispatch(articleActions.setArticle({ articleId: params?.articleId, setArticle }));
} else {
setArticle({});
}
}, [dispatch, params?.articleId]);
return (
<div>
{ boardStatus === 200 && boardList.length > 0 ?
(
<>
<div>
<span>게시판: </span>
<select
name="boardId"
onChange={onChangeArticle}
value={article?.boardId ?? 0}
>
<option value={SELECT.id} key={SELECT.id}>{SELECT.name}</option>
{
boardList.map((board, index) => (
<option value={board?.id} key={board?.id}>{board?.name ?? ""}</option>
))
}
</select>
</div>
<div>
<span>제목: </span>
<input
name="title"
onChange={onChangeArticle}
value={article?.title ?? ""}
/>
</div>
<div>
<span>내용: </span>
<textarea
name="content"
onChange={onChangeArticle}
value={article?.content ?? ""}
/>
</div>
<button onClick={onClickSubmitButton}>등록</button>
</>
) : boardStatus === 200 && boardList.length === 0 ?
(
<div>
<div>
게시판 등록이 필요합니다.
</div>
<div>
<button onClick={onClickMoveToControlButton}>설정 이동</button>
</div>
</div>
) : (
<div>
<div>
<span>{boardStatus}</span>
</div>
<div>
<span>{boardStatusText}</span>
</div>
</div>
)
}
{ articleStatus !== 200 && articleStatus !== 0 && (
<div>
<div>
<span>{articleStatus}</span>
</div>
<div>
<span>{articleStatusText}</span>
</div>
</div>
)}
</div>
);
}
export default Post;
Post.js 의 status는 boardReducer에서 가져오는 부분이기 때문에 articleReducer의 status를 사용하려면 변수명이 겹쳐 사용할 수 없었다.
그래서 useSelector 안 에서 이름을 바꿔서 값을 들고왔다.
const { boardList, boardStatus, boardStatusText } = useSelector(
(state) => ({
boardList: state.boardReducer.boardList,
boardStatus: state.boardReducer.status,
boardStatusText: state.boardReducer.statusText
}));
const { articleStatus, articleStatusText } = useSelector(
(state) => ({
articleStatus: state.articleReducer.status,
articleStatusText: state.articleReducer.statusText
}));
맨 아래 붙어있는 articleStatus 는 등록이나 수정과정에서 실패할 경우 추가로 뜰 공간이다.
{ articleStatus !== 200 && articleStatus !== 0 && (
<div>
<div>
<span>{articleStatus}</span>
</div>
<div>
<span>{articleStatusText}</span>
</div>
</div>
)}
게시판이 모두 미리 등록이 되어있어야 게시글 등록도 가능하므로 설정 화면을 만들게 되었다.
그래서 게시글 등록시 사용할 게시판이 없다면 설정화면으로 바로 이동할 수 있도록 (사용자 편의를 위하여) 버튼을 추가한 것이다.
function onClickMoveToControlButton() {
history.push("/control");
}
<button onClick={onClickMoveToControlButton}>설정 이동</button>
------ 4/15 추가
/src/views/Post.js 에 추가사항이 생겼다.
게시판명 combo box 부분은 나중에 설정에서 새로 등록된 게시판 혹은 이름이 수정된 게시판, 삭제된 게시판을 반영시켜 나타나야 하기 때문에 게시글 등록/수정하는 Post 컴포넌트가 렌더링 될 때마다 게시판 목록을 조회해줘야 한다.
따라서 useEffect 부분에 액션 dispatch를 추가하겠다.
boardAction을 사용하므로 import 도 해줘야 한다.
// Post.js
import { boardActions } from '../slices/boardSlice';
useEffect(() => {
dispatch(boardActions.getBoardList());
if (params?.articleId) {
dispatch(articleActions.setArticle({ articleId: params?.articleId, setArticle }));
} else {
setArticle({});
}
}, [dispatch, params?.articleId]);
수정이 반영된 풀 코드는 다음과 같다.
// Post.js
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SELECT } from '../utils/events';
import { articleActions } from '../slices/articleSlice';
import { boardActions } from '../slices/boardSlice';
import { useHistory, useParams } from 'react-router';
function Post() {
const { boardList, boardStatus, boardStatusText } = useSelector(
(state) => ({
boardList: state.boardReducer.boardList,
boardStatus: state.boardReducer.status,
boardStatusText: state.boardReducer.statusText
}));
const { articleStatus, articleStatusText } = useSelector(
(state) => ({
articleStatus: state.articleReducer.status,
articleStatusText: state.articleReducer.statusText
}));
const [ article, setArticle ] = useState({});
const dispatch = useDispatch();
const history = useHistory();
const params = useParams();
function onChangeArticle(e) {
setArticle({
...article,
[e.currentTarget.name]: e.currentTarget.value
});
}
function onClickSubmitButton() {
if (article?.boardId > 0 && article?.title)
{
if (article?.id > 0)
{
dispatch(articleActions.putArticle(article));
} else {
dispatch(articleActions.postArticle(article));
}
} else {
alert("게시판과 제목은 필수값입니다.");
}
}
function onClickMoveToControlButton() {
history.push("/control");
}
useEffect(() => {
dispatch(boardActions.getBoardList());
if (params?.articleId) {
dispatch(articleActions.setArticle({ articleId: params?.articleId, setArticle }));
} else {
setArticle({});
}
}, [dispatch, params?.articleId]);
return (
<div>
{ boardStatus === 200 && boardList.length > 0 ?
(
<>
<div>
<span>게시판: </span>
<select
name="boardId"
onChange={onChangeArticle}
value={article?.boardId ?? 0}
>
<option value={SELECT.id} key={SELECT.id}>{SELECT.name}</option>
{
boardList.map((board, index) => (
<option value={board?.id} key={board?.id}>{board?.name ?? ""}</option>
))
}
</select>
</div>
<div>
<span>제목: </span>
<input
name="title"
onChange={onChangeArticle}
value={article?.title ?? ""}
/>
</div>
<div>
<span>내용: </span>
<textarea
name="content"
onChange={onChangeArticle}
value={article?.content ?? ""}
/>
</div>
<button onClick={onClickSubmitButton}>등록</button>
</>
) : boardStatus === 200 && boardList.length === 0 ?
(
<div>
<div>
게시판 등록이 필요합니다.
</div>
<div>
<button onClick={onClickMoveToControlButton}>설정 이동</button>
</div>
</div>
) : (
<div>
<div>
<span>{boardStatus}</span>
</div>
<div>
<span>{boardStatusText}</span>
</div>
</div>
)
}
{ articleStatus !== 200 && articleStatus !== 0 && (
<div>
<div>
<span>{articleStatus}</span>
</div>
<div>
<span>{articleStatusText}</span>
</div>
</div>
)}
</div>
);
}
export default Post;
목차 돌아가기:
binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial-v2