Binary Journey

[React][CRUD] 게시판 만들기 All in One (7). 새 글 쓰기, axios, post 본문

React/게시판만들기 v2.

[React][CRUD] 게시판 만들기 All in One (7). 새 글 쓰기, axios, post

binaryJournalist 2021. 4. 14. 17:14
반응형

 

목차돌아가기:

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

 

 

이제까지 거의 GET방식을 이용한 조회를 해봤다 (조회수 update, 댓글 등록, 댓글삭제 제외)

 

 

이번엔 새 글 쓰기를 해보겠다.

 

새 글 쓰기는 지난번 Route.js 에서

경로가 "/insert" 일 때 컴포넌트는 Post 로 렌더링되도록 설정해놓았다.

 

 

 

새글 쓰기는 어디에서나 가능하도록 하려 한다. 그래서 Views.js 에 id=header 인 태그 부분에 이벤트함수를 다음과 같이 집어넣었다.

 

 

// Views.js

import React from 'react';
import Board from "./views/Board";
import Routes from "./routes/Routes";
import "./Views.css";
import { useHistory } from 'react-router';

function Views() {
    const history = useHistory();

    function onClickNewPostButton() {
        history.push("/insert");
    }

    function onClickControlButton() {
        history.push("/control");
    }

    return (
        <div >
            <div id="header" className="header">
                <div >
                    <h3>Board CRUD</h3>
                </div>
                <div>
                    <div>
                        <button onClick={onClickNewPostButton}>새글</button>
                    </div>
                </div>
            </div>
            <div id="sidebar" className="sidebar">
                <Board />
            </div>
            <div id="content" className="content">
                <Routes />
            </div>
        </div>
    );
}

export default Views;

 

 

 

버튼을 누르면 이렇게 될 것이다.

 

 

이제 Post를 좀 그럴듯한 글쓰기 화면으로 바꾸겠다

 

 

// Posts.js

import React from 'react';

function Post() {
    return (
        <div>
            <div>
                <span>게시판: </span>
                <select>
                    <option>선택</option>
                    <option></option>
                    <option></option>
                </select>
            </div>
            <div>
                <span>제목: </span><input></input>
            </div>
            <div>
            <span>내용: </span><textarea></textarea>
            </div>
        </div>
    );
}

export default Post;

 

 

 

 

(이번에는 게시판 고르는 combo box도 추가되었다.)

 

 

게시글 쓰는 부분도 useState로만 진행하려고 한다.

 

redux 의 useSelector 를 쓰는 부분은 게시판 combo box에만 사용할 것 같다.

 

나는 게시판 목록을 서버에서 다시 불러와서 쓸 게 아니라 state에 있는 boardList 다시 불러와서 쓸 계획이었는데

 

화면 구조 상 게시판 정보가 없어도 Header 부분 새글 버튼만 눌러도 Post로 이동이 가능하므로

Post 에 게시판이 없을 때 보여 줄 JSX를 만들어야 겠다.

 

 

나는 이렇게 바꿔주었다.

 

// Post.js

import React from 'react';
import { useSelector } from 'react-redux';

function Post() {
    const { boardList, status, statusText } = useSelector((state) => state.boardReducer);
    
    return (
            <div>
                { status === 200 && boardList.length > 0 ?
                    (
                        <>
                            <div>
                                <span>게시판: </span>
                                <select>
                                    <option>선택</option>
                                    <option></option>
                                    <option></option>
                                </select>
                            </div>
                            <div>
                                <span>제목: </span><input></input>
                            </div>
                            <div>
                                <span>내용: </span><textarea></textarea>
                            </div>
                        </>
                    ) : status === 200 && boardList.length === 0 ?
                    (
                        <div>
                            게시판 등록이 필요합니다.
                        </div>
                    ) : (
                        <div>
                            <div>
                                <span>{status}</span>
                            </div>
                            <div>
                                <span>{statusText}</span>
                            </div>
                        </div>
                    )
                }
            </div>
    );
}

export default Post;

 

 

이제 combo box 를 바꿔줄 차례인데 나는 combo box 와 관련해서 Constants를 아예 utils 에 공통으로 쓸 수 있도록 만들었다.

 

/utils/events.js

export const SELECT = { id: 0, name: "선택" };
export const WHOLE = { id: 0, name: "전체" };

 

(소스를 다시 정리해보니 event.js 는 결국 안 썼다)

 

// Post.js

import React from 'react';
import { useSelector } from 'react-redux';
import { SELECT } from '../utils/events';

function Post() {
    const { boardList, status, statusText } = useSelector((state) => state.boardReducer);
    
    return (
            <div>
                { status === 200 && boardList.length > 0 ?
                    (
                        <>
                            <div>
                                <span>게시판: </span>
                                <select name="boardId">
                                    <option value={SELECT.id}>{SELECT.name}</option>
                                    { 
                                        boardList.map((board) => (
                                            <option value={board?.id}>{board?.name ?? ""}</option>
                                        ))
                                    }
                                </select>
                            </div>
                            <div>
                                <span>제목: </span><input></input>
                            </div>
                            <div>
                                <span>내용: </span><textarea></textarea>
                            </div>
                        </>
                    ) : status === 200 && boardList.length === 0 ?
                    (
                        <div>
                            게시판 등록이 필요합니다.
                        </div>
                    ) : (
                        <div>
                            <div>
                                <span>{status}</span>
                            </div>
                            <div>
                                <span>{statusText}</span>
                            </div>
                        </div>
                    )
                }
            </div>
    );
}

export default Post;

 

 

게시판 combo box에 값이 들어가있는지 확인해보자

 

 

useState로 object 값 넣을 수 있는지 해봤는데 되길래 소스를 올린다.

 

// Post.js

import React, { useState } from 'react';
import { useSelector } from 'react-redux';
import { SELECT } from '../utils/events';

function Post() {
    const { boardList, status, statusText } = useSelector((state) => state.boardReducer);
    const [ article, setArticle ] = useState({});

    function onChangeArticle(e) {
        setArticle({
            ...article,
            [e.currentTarget.name]: e.currentTarget.value
        });
    }
    
    return (
            <div>
                { status === 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>
                        </>
                    ) : status === 200 && boardList.length === 0 ?
                    (
                        <div>
                            게시판 등록이 필요합니다.
                        </div>
                    ) : (
                        <div>
                            <div>
                                <span>{status}</span>
                            </div>
                            <div>
                                <span>{statusText}</span>
                            </div>
                        </div>
                    )
                }
            </div>
    );
}

export default Post;

 

 

input에 값 집어넣기가 되니 이제 서버에 보내보자

 

게시판 태그들을 감쌌던 <></> 안에 등록 버튼을 만들어준다. 

 

 

// Post.js

<>
        <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>등록</button>
</>

 

 

articleSlice 와 articleSaga 에 등록과 관련된 액션과 함수들을 만든다.

 

// articleSlice.js

postArticle: (state, action) => {},
postArticleSuccess: (state, action) => {},
postArticleFail: (state, action) => {
    state.status = action.payload?.status ?? 500;
    state.statusText = action.payload?.statusText ?? "Network Error";
},
// articleSaga.js

function apiPostArticle(requestBody) {
    return axios.post(`articles/`, requestBody);
}

function* asyncPostArticle(action) {
    try {
        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* watchPostArticle() {
    while(true) {
        const action = yield take(articleActions.postArticle);
        yield call(asyncPostArticle, action);
    }
}

export default function* articleSaga()
{
    yield all([fork(watchGetArticleList), fork(watchGetArticle), fork(watchUpdateArticleViews), fork(watchPostArticle)]);
}

 

등록 실패시 새로고침하여 내용을 다 날려버리는 것은 선택 사항이다.

 

필요하다면 history.go(0); 쓰면 된다.

 

 

그리고 history와 관련하여

 

utils 에 있는 history를 import하는 게 싫다면

 

import { all, call, retry, fork, put, take, select, getContext } from 'redux-saga/effects';

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}`);
    }
}

 

store 의 sagaMiddleware 에서 넣은 context: { history: history } 를 getContext("history") 로 꺼내와서 쓰면 된다.

초반에 말했다시피 Router 가 BrowserRouter 가 아니면 된다.

 

 

 

views/Post.js 는 다음과 같다

// Post.js

import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { SELECT } from '../utils/events';
import { articleActions } from '../slices/articleSlice';

function Post() {
    const { boardList, status, statusText } = useSelector((state) => state.boardReducer);
    const [ article, setArticle ] = useState({});
    const dispatch = useDispatch();

    function onChangeArticle(e) {
        setArticle({
            ...article,
            [e.currentTarget.name]: e.currentTarget.value
        });
    }

    function onClickSubmitButton() {
        if (article?.boardId > 0 && article?.title)
        {
            dispatch(articleActions.postArticle(article));
        } else {
            alert("게시판과 제목은 필수값입니다.");
        }
    }

    
    return (
            <div>
                { status === 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>
                        </>
                    ) : status === 200 && boardList.length === 0 ?
                    (
                        <div>
                            게시판 등록이 필요합니다.
                        </div>
                    ) : (
                        <div>
                            <div>
                                <span>{status}</span>
                            </div>
                            <div>
                                <span>{statusText}</span>
                            </div>
                        </div>
                    )
                }
            </div>
    );
}

export default Post;

 

 

내용 잘 나오는지 확인해보자. url에도 아이디가 잘 들어가는지 보자

 

 

 

 

게시판 메뉴 눌렀을 때 목록으로도 잘 나오는지 보자

 

 

 

 

 

 

 

목차돌아가기:

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

 

반응형