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

 

[React][CRUD] create-board-tutorial-v2

* 인용시 출처 부탁드립니다. 완성 소스 code: github.com/cruellaDev/react-create-board-v2 cruellaDev/react-create-board-v2 updated version of react-create-board. Contribute to cruellaDev/react-create-..

binaryjourney.tistory.com

 

 

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

 

[React][CRUD] create-board-tutorial-v2

* 인용시 출처 부탁드립니다. 완성 소스 code: github.com/cruellaDev/react-create-board-v2 cruellaDev/react-create-board-v2 updated version of react-create-board. Contribute to cruellaDev/react-create-..

binaryjourney.tistory.com

 

반응형