반응형

 

목차 돌아가기:

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

 

 

수정으로 들어가기 전에 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

 

[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

 

반응형

+ Recent posts