React

[React] redux를 이용하여 게시판 만들기(3) - 떽떽대는 개발공부

떽이 2021. 1. 27. 18:40

2021/01/26 - [React] - [React] redux를 이용하여 게시판 만들기(2) - 떽떽대는 개발공부

 

[React] redux를 이용하여 게시판 만들기(2) - 떽떽대는 개발공부

2021/01/25 - [React] - [React] redux 를 이용하여 게시판 만들기(1) - 떽떽대는 개발공부 [React] redux 를 이용하여 게시판 만들기(1) - 떽떽대는 개발공부 2021/01/24 - [React] - [React] react-router-dom,..

ddeck.tistory.com

 

 

 

지난 글에서 글 목록 클릭 시 해당 글 상세보기 페이지 구현하기를 하였다.

이번 글에서는 상세보기 페이지에서 글 수정 및 삭제 기능을 추가 할 것이다.

src/components/BoardContent.js

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux'
import { editContent, removeContent } from '@modules/boardReducer'
import { useHistory } from 'react-router-dom';

function BoardContent() {
    const { selectRowData } = useSelector(state => state.boardReducer)

	// input 값을 변경할 수 있도록 제어
    const [title, setTitle] = useState(selectRowData.title)
    const [content, setContent] = useState(selectRowData.content)

	// input 값 변경될때마다 값을 변경
    const handleTitle = (e) => {
        setTitle(e.target.value)
    }

    const handleContent = (e) => {
        setContent(e.target.value)
    }

	// 함수형 컴포넌트에서 useDispatch를 사용할 수 있도록 선언
    const dispatch = useDispatch()
    // 함수형 컴포넌트에서 useHistory 를 사용할 수 있도록 선언
    const history = useHistory();

    const onChange = () => {
    	// reducer의 data와 동일한 형식으로 맞춰준다.
        const _inputData = {
            id: selectRowData.id,
            title: title,
            content: content
        }
		// reducer 의 edirContent 함수에 변경값 전달
        dispatch(editContent(_inputData))
        // input 란 초기화
        setTitle('')
        setContent('')
        // BoardList 페이지로 이동
        history.push('/')
    }

    return(
        <div>
            <h2>상세보기</h2>
            <div>
                <div>
                    <input type='text' className='inputTitle' onChange={handleTitle} value={title} />
                </div>
                <div>
                    <textarea className='inputContent' onChange={handleContent} value={content} />
                </div>
                <div>
                    <button type='button' onClick={onChange} className='editBtn'>edit</button>
                    <button type='button' className='deleteBtn'>delete</button>
                </div>
            </div>
        </div>
    )
}

export default BoardContent;

 

먼저 BoardContent 에서 input 란 수정할 수 있도록 value 값을 setTitle, setContent 로 제어 해준다.

input 값이 변경될 때마다 handleTitle, handleContent 함수를 호출하여 setTitle, setContent 로 실시간 값을 변경해 주고, edit 버튼을 클릭하면 onChange 함수를 호출하게 된다.

onChange 함수에서는 boardReducer 의 inputData 의 형식에 맞추어 데이터를 구성하고, editContent 함수를 호출하여 값을 전달한다.

dispatch 완료 후 화면 전환을 위해 useHistory를 사용하여 list 페이지로 이동한다. 페이지 이동 시 새로고침 되면 기존의 데이터가 날아가게 되므로 useHistory 를 사용한다.

이제 boardReducer.js 를 수정해준다.

src/modules/boardReducer.js

const _SAVE = 'DATA_SAVE';
const _SELECT = 'DATA_SELECT'
const _EDIT = 'DATA_EDIT'

export const dataSave = (inputData) => ({
    type: _SAVE,
    inputData: {
        id: inputData.id,
        title: inputData.title,
        content: inputData.content
    }
})

export const selectRow = (id) => (
    console.log('reducer :: id :: ', id),
    {
    type: _SELECT,
    inputData: {
        id: id,
    }
})

// boardContent 에서 받아온 값
export const editContent = (inputData) => ({
    type: _EDIT,
    inputData: {
        id: inputData.id,
        title: inputData.title,
        content: inputData.content
    }
})

const initialState = {
    lastId: 0,
    inputData: [
        {
            id: '',
            title: '',
            content: ''
        }
    ],
    selectRowData: {}
}

export default function boardReducer(state = initialState, action){
    switch(action.type) {
        case _SAVE:
            console.log(state.inputData)
            return {
                lastId: state.lastId + 1,
                inputData: state.inputData.concat({
                    ...action.inputData,
                    id: state.lastId + 1,
                })
            }
        case _SELECT:
            console.log(action)
            return {
                ...state,
                selectRowData: state.inputData.find(row => row.id === action.inputData.id)
            }
        case _EDIT:
            return {
                ...state,
                // state 에 저장되어 있는 inputData 중 동일한 id 값을 가진 데이터를 action.inputData 값으로 변경해준다.
                inputData: state.inputData.map(row =>
                    row.id === action.inputData.id ?
                    {...action.inputData} : row    
                ),
                selectRowData: {}
            }
        default:
            return state
    }
}

 

이렇게 하면 글 수정하는 것까지 완료 되었다. 마지막으로 글 삭제 부분을 해보자.

src/components/BoardContent.js

import React, { useState } from 'react';
import { useSelector, useDispatch } from 'react-redux'
import { editContent, removeContent } from '@modules/boardReducer'
import { useHistory } from 'react-router-dom';

function BoardContent() {
    const { selectRowData } = useSelector(state => state.boardReducer)

    const [title, setTitle] = useState(selectRowData.title)
    const [content, setContent] = useState(selectRowData.content)

    const handleTitle = (e) => {
        setTitle(e.target.value)
    }

    const handleContent = (e) => {
        setContent(e.target.value)
    }

    const dispatch = useDispatch()
    const history = useHistory();

    const onChange = () => {
        const _inputData = {
            id: selectRowData.id,
            title: title,
            content: content
        }
        console.log('clickSave :: ', _inputData)
        dispatch(editContent(_inputData))
        setTitle('')
        setContent('')
        history.push('/')
    }

    const onRemove = () => {
    	// reducer의 removeContent 함수에 삭제할 id 값을 전달한다.
        dispatch(removeContent(selectRowData.id))
        // input 값 초기화
        setTitle('')
        setContent('')
        // 페이지 이동
        history.push('/')
    }

    return(
        <div>
            <h2>상세보기</h2>
            <div>
                <div>
                    <input type='text' className='inputTitle' onChange={handleTitle} value={title} />
                </div>
                <div>
                    <textarea className='inputContent' onChange={handleContent} value={content} />
                </div>
                <div>
                    <button type='button' onClick={onChange} className='editBtn'>edit</button>
                    <button type='button' onClick={onRemove} className='deleteBtn'>delete</button>
                </div>
            </div>
        </div>
    )
}

export default BoardContent;

 

delete 버튼 클릭 시 onRemove 함수가 호출되고 reducer 의 removeContent 함수에 dispatch 된다.

이때 전달할 id 는 useSelector 에서 받아온 id 값을 그대로 사용한다.

이제 boardReducer.js 만 에서 추가 수정해주자.

src/modules/boardReducer.js

const _SAVE = 'DATA_SAVE';
const _SELECT = 'DATA_SELECT'
const _EDIT = 'DATA_EDIT'
const _DELETE = 'DATA_DELETE'

export const dataSave = (inputData) => ({
    type: _SAVE,
    inputData: {
        id: inputData.id,
        title: inputData.title,
        content: inputData.content
    }
})

export const selectRow = (id) => (
    console.log('reducer :: id :: ', id),
    {
    type: _SELECT,
    inputData: {
        id: id,
    }
})

export const editContent = (inputData) => (
    console.log('reducer :: edit :: ', inputData),
    {
    type: _EDIT,
    inputData: {
        id: inputData.id,
        title: inputData.title,
        content: inputData.content
    }
})

// 삭제할 id값을 받아온다.
export const removeContent = (id) => ({
    type: _DELETE,
    inputData: {
        id: id,
    }
})

const initialState = {
    lastId: 0,
    inputData: [
        {
            id: '',
            title: '',
            content: ''
        }
    ],
    selectRowData: {}
}

export default function boardReducer(state = initialState, action){
    switch(action.type) {
        case _SAVE:
            console.log(state.inputData)
            return {
                lastId: state.lastId + 1,
                inputData: state.inputData.concat({
                    ...action.inputData,
                    id: state.lastId + 1,
                })
            }
        case _SELECT:
            console.log(action)
            return {
                ...state,
                selectRowData: state.inputData.find(row => row.id === action.inputData.id)
            }
        case _EDIT:
            return {
                ...state,
                inputData: state.inputData.map(row =>
                    row.id === action.inputData.id ?
                    {...action.inputData} : row    
                ),
                selectRowData: {}
            }
        case _DELETE:
            return {
            	// lastId 값이 현재 삭제 요청된 id값과 동일하면 값을 줄여준다.
                lastId: state.lastId === action.inputData.id ? state.lastId - 1 : state.lastId,
                // filter를 사용하여 state에 있는 값과 action.id 값이 동일하지 않은 값만 return 하여 state 에 저장해준다.
                inputData: state.inputData.filter(row =>
                    row.id !== action.inputData.id
                ),
                selectRowData: {}
            }
        default:
            return state
    }
}

 

이렇게 하면 간단하게 reducer를 이용한 게시판 만들기가 완료 된다.

처음엔 reducer 의 개념도 이해 못했는데 여러 예제를 통해 사용법을 익혀보니 크게 어렵지 않게 만들 수 있게 되어 기쁘다.

순수 함수의 개념에 대해서는 추가적으로 공부가 필요할 것 같다.