Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
Tags
- createSlice
- SW
- Python
- axios
- react
- 이코테
- Algorithm
- 프로그래머스
- C++
- 테코테코
- redux-saga
- 항해99
- 매일메일
- programmers
- react-redux
- Get
- redux
- react-router
- 항해플러스
- 코딩테스트합격자되기
- redux-toolkit
- 알고리즘
- maeil-mail
- 자바
- java
- useDispatch
- JavaScript
- sw expert academy
- 리액트
- json-server
Archives
- Today
- Total
Binary Journey
[React][CRUD] 게시판 만들기 All in One (9). 글 삭제, axios, delete 본문
React/게시판만들기 v2.
[React][CRUD] 게시판 만들기 All in One (9). 글 삭제, axios, delete
binaryJournalist 2021. 4. 14. 17:35반응형
목차 돌아가기:
binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial-v2
articleSlice.js 에 삭제 액션을 만들어준다.
// articleSlice.js
deleteArticle: (state, action) => {},
deleteArticleSuccess: (state, action) => {
state.article = initialState.article;
state.status = action.payload?.status;
state.statusText = action.payload?.statusText ?? "";
},
deleteArticleFail: (state, action) => {
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
articleSaga.js에 메 api함수, async함수를 만들어준다.
// articleSaga.js
function apiDeleteArticle(articleId) {
return axios.delete(`articles/${articleId}`);
}
function* asyncDeleteArticle() {
try {
const article = yield select((state) => state.articleReducer.article);
const response = yield call(apiDeleteArticle, article?.id ?? 0);
if (response?.status === 200) {
yield put(articleActions.deleteArticleSuccess());
alert("삭제되었습니다!");
history.push(`/board/${article?.boardId ?? 0}`);
} else {
yield put(articleActions.deleteArticleFail(response));
}
} catch(e) {
console.error(e);
yield put(articleActions.deleteArticleFail(e?.response));
yield alert(`삭제실패 \n Error: ${e?.response?.status}, ${e?.response?.statusText}`);
}
}
function* watchDeleteArticle() {
while(true) {
yield take(articleActions.deleteArticle);
yield call(asyncDeleteArticle);
}
}
export default function* articleSaga()
{
yield all([fork(watchGetArticleList), fork(watchGetArticle),
fork(watchUpdateArticleViews), fork(watchPostArticle),
fork(watchSetArticle), fork(watchPutArticle), fork(watchDeleteArticle)]);
}
삭제 버튼을 Article.js 에 만들어준다.
// Article.js
function onClickDeleteButton() {
dispatch(articleActions.deleteArticle());
}
<div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
끝이다!
이제 손댈 데가 없는 Article.js, articleSlice.js, articleSaga.js 모습을 공개한다.
// Article.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { useHistory, useParams } from 'react-router-dom';
import Comment from './Comments';
import { articleActions } from '../slices/articleSlice';
function Article() {
const params = useParams();
const { article, status, statusText } = useSelector((state) => state.articleReducer);
const boardList = useSelector((state) => state.boardReducer.boardList);
const dispatch = useDispatch();
const history = useHistory();
function onClickUpdateButton() {
history.push(`/update/${params?.articleId ?? 0}`);
}
function onClickDeleteButton() {
if (!window.confirm("삭제하시겠습니까?")) return false;
dispatch(articleActions.deleteArticle());
}
useEffect(() => {
dispatch(articleActions.getArticle(params?.articleId ?? 0));
}, [dispatch, params?.articleId]);
return (
<>
{
status === 200 ?
<>
<div>
<button onClick={onClickUpdateButton}>수정</button>
</div>
<div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
<div>
<span>게시판: </span>
<span>
{
boardList.length > 0 &&
boardList.find((board) => board.id === parseInt(article?.boardId))?.name
}
</span>
</div>
<div>
<div><span>제목: </span><span>{article?.title ?? ""}</span></div>
<div><span>조회수: </span><span>{article?.views ?? ""}</span></div>
<div><span>작성일시: </span><span>{(article.insertDate) ? new Date(article?.insertDate).toLocaleString() : ""}</span></div>
<div><span>내용: </span><span>{article?.content ?? ""}</span></div>
</div>
<div>
<Comment articleId={params?.articleId ?? 0} />
</div>
</>
:
<div>
<div>
<span>{status}</span>
</div>
<div>
<span>{statusText}</span>
</div>
</div>
}
</>
);
}
export default Article;
// articleSlice.js
import { createSlice } from "@reduxjs/toolkit";
const name = "article";
const initialState = {
article: {},
articleList: [],
status: 0,
statusText: "Loading",
};
const reducers = {
getArticleList: (state, action) => {},
getArticleListSuccess: (state, action) => {
state.articleList = action.payload?.data ?? [];
state.status = action.payload?.status;
state.statusText = action.payload?.statusText ?? "Success";
},
getArticleListFail: (state, action) => {
state.articleList = initialState.articleList;
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
getArticle: (state, action) => {},
getArticleSuccess: (state, action) => {},
getArticleFail: (state, action) => {
state.article = initialState.article;
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
updateArticleViews: (state, action) => {},
updateArticleViewsSuccess: (state, action) => {
state.article = action.payload?.data ?? {};
state.status = action.payload?.status;
state.statusText = action.payload?.statusText ?? "Success";
},
updateArticleViewsFail: (state, action) => {
state.article = initialState.article;
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
postArticle: (state, action) => {},
postArticleSuccess: (state, action) => {},
postArticleFail: (state, action) => {
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
setArticle: (state, action) => {},
putArticle: (state, action) => {},
putArticleSuccess: (state, action) => {},
putArticleFail: (state, action) => {
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
deleteArticle: (state, action) => {},
deleteArticleSuccess: (state, action) => {
state.article = initialState.article;
state.status = action.payload?.status;
state.statusText = action.payload?.statusText ?? "Success";
},
deleteArticleFail: (state, action) => {
state.status = action.payload?.status ?? 500;
state.statusText = action.payload?.statusText ?? "Network Error";
},
};
const articleSlice = createSlice({
name,
initialState,
reducers,
});
export const articleReducer = articleSlice.reducer;
export const articleActions = articleSlice.actions;
// articleSaga.js
import { all, call, retry, fork, put, take, select, getContext } from 'redux-saga/effects';
import { articleActions } from '../slices/articleSlice';
import axios from '../utils/axios';
import history from '../utils/history';
import qs from "query-string";
const SECOND = 1000;
// api 서버 연결 주소
function apiGetArticle(articleId) {
return axios.get(`articles/${articleId}`);
}
function apiGetArticleList(requestParams) {
return axios.get(`articles?${qs.stringify(requestParams)}`);
}
function apiPutArticle(requestBody) {
return axios.put(`articles/${requestBody?.id}`, requestBody);
}
function apiPostArticle(requestBody) {
return axios.post(`articles/`, requestBody);
}
function apiDeleteArticle(articleId) {
return axios.delete(`articles/${articleId}`);
}
// api 서버 연결 후 action 호출
function* asyncGetArticleList(action) {
try {
// const response = yield call(apiGetArticleList, { boardId: action.payload });
const response = yield retry(3, 10 * SECOND, apiGetArticleList, { boardId: action.payload });
if (response?.status === 200) {
yield put(articleActions.getArticleListSuccess(response));
} else {
yield put(articleActions.getArticleListFail(response));
}
} catch(e) {
yield put(articleActions.getArticleListFail(e.response));
}
}
function* asyncGetArticle(action) {
try {
const response = yield call(apiGetArticle, action.payload);
if (response?.status === 200) {
yield put(articleActions.getArticleSuccess()); // 조회 성공확인만 판단하는 용도로 남김
yield put(articleActions.updateArticleViews(response.data));
} else {
yield put(articleActions.getArticleFail(response));
}
} catch(e) {
console.error(e);
yield put(articleActions.getArticleFail(e.response));
}
}
function* asyncUpdateArticleViews(action) {
try {
const response = yield call(apiPutArticle, {
...action.payload,
views: parseInt(action.payload?.views ?? 0) + 1,
updateDate: Date.now()
});
if (response?.status === 200) {
yield put(articleActions.updateArticleViewsSuccess(response));
} else {
yield put(articleActions.updateArticleViewsFail(response));
}
} catch(e) {
console.error(e);
yield put(articleActions.updateArticleViewsFail(e?.response));
}
}
function* asyncPostArticle(action) {
try {
// const history = yield getContext("history"); // react-router-dom 의 BrowserRouter 에서는 안 됨
const response = yield call(apiPostArticle, {
...action.payload,
id: 0,
views: 0,
insertDate: Date.now(),
updateDate: Date.now()
});
if (response?.status === 201) {
yield put(articleActions.postArticleSuccess());
history.push(`/article/${response?.data?.id ?? 0}`);
} else {
yield put(articleActions.postArticleFail(response));
yield alert(`등록실패 \n Error: ${response.status}, ${response.statusText}`);
}
} catch(e) {
console.error(e);
yield put(articleActions.postArticleFail(e?.response));
yield alert(`등록실패 \n Error: ${e?.response?.status}, ${e?.response?.statusText}`);
}
}
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* 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* asyncDeleteArticle() {
try {
const article = yield select((state) => state.articleReducer.article);
const response = yield call(apiDeleteArticle, article?.id ?? 0);
if (response?.status === 200) {
yield put(articleActions.deleteArticleSuccess());
alert("삭제되었습니다!");
history.push(`/board/${article?.boardId ?? 0}`);
} else {
yield put(articleActions.deleteArticleFail(response));
}
} catch(e) {
console.error(e);
yield put(articleActions.deleteArticleFail(e?.response));
yield alert(`삭제실패 \n Error: ${e?.response?.status}, ${e?.response?.statusText}`);
}
}
// action 호출을 감시하는 watch 함수
function* watchGetArticleList() {
while(true) {
const action = yield take(articleActions.getArticleList);
yield call(asyncGetArticleList, action);
}
}
function* watchGetArticle() {
while(true) {
const action = yield take(articleActions.getArticle);
yield call(asyncGetArticle, action);
}
}
function* watchUpdateArticleViews() {
while(true) {
const action = yield take(articleActions.updateArticleViews);
yield call(asyncUpdateArticleViews, action);
}
}
function* watchPostArticle() {
while(true) {
const action = yield take(articleActions.postArticle);
yield call(asyncPostArticle, action);
}
}
function* watchSetArticle() {
while(true) {
const action = yield take(articleActions.setArticle);
yield call(asyncSetArticle, action);
}
}
function* watchPutArticle() {
while(true) {
const action = yield take(articleActions.putArticle);
yield call(asyncPutArticle, action);
}
}
function* watchDeleteArticle() {
while(true) {
yield take(articleActions.deleteArticle);
yield call(asyncDeleteArticle);
}
}
export default function* articleSaga()
{
yield all([fork(watchGetArticleList), fork(watchGetArticle),
fork(watchUpdateArticleViews), fork(watchPostArticle),
fork(watchSetArticle), fork(watchPutArticle), fork(watchDeleteArticle)]);
}
글 등록, 수정, 삭제가 이제까지 제대로 안 됐다면 소스를 다시 확인해 보자!
목차 돌아가기:
binaryjourney.tistory.com/pages/ReactCRUD-create-board-tutorial-v2
반응형