반응형

지난 포스팅에서 멘붕이 와서 중단한 일을 이번 포스팅에서 이어서 진행하겠다.

 

먼저, 해야 할 일과 완료된 일을 다시 정리해보자.

 

a) 작성 완료 버튼 event 추가

b) 제목과 내용을 DB에 insert/update 하는 서버 API 코드 작성

c) 수정하기 버튼 event 추가

d) 게시글 상세 조회 select 하는 서버 API 코드 작성

e) 삭제하기 버튼 event 추가

f) 게시글 삭제 delete 하는 서버 API 코드 작성

 

 

1. 수정하기 버튼 event 추가

1-1. 목록의 체크박스가 선택된 상태 -> 수정하기 버튼 클릭 시 해당 글의 상세 내용이 하단에 표시되도록 설정

1-2 목록의 체크박스가 여러 개 선택된 상태 -> 수정하기 버튼 클릭 시 경고창

1-3 목록의 체크박스가 선택되지 않은 상태 -> 수정하기 버튼 클릭 시 경고창

 

체크박스 선택 시 해당 글의 ID 값이 매핑될 수 있도록 목록 렌더링 부분을 먼저 수정하자.

BoardList.tsx 파일에 Board 메서드를 약간 수정한다.

 

Board 메서드에서 컴포넌트 state값을 관리하기 위해서 props를 추가하였다.

 

const Board = ({
    id,
    title,
    registerId,
    registerDate,
    props,
}: {
    id: number;
    title: string;
    registerId: string;
    registerDate: string;
    props: any;
}) => {
    return (
        <tr>
            <td>
                <input type="checkbox" value={id} onChange={props.onCheckboxChange}></input>
            </td>
            <td>{id}</td>
            <td>{title}</td>
            <td>{registerId}</td>
            <td>{registerDate}</td>
        </tr>
    );
};

 

렌더링 하는 부분에서는 아래와 같이 props를 넘긴다.

 

// eslint-disable-next-line
boardList.map((v: any) => {
    return (
        <Board
            id={v.BOARD_ID}
            title={v.BOARD_TITLE}
            registerId={v.REGISTER_ID}
            registerDate={v.REGISTER_DATE}
            key={v.BOARD_ID}
            props={this}
        />
    );
})

 

이 내용은 컴포넌트 간 부모-자식 관계에서도 많이 사용하는 내용이기 때문에 알아둬야겠다.

 

그리고 체크박스 상태 변경 시에 호출되고 있는 onCheckboxChange 메서드는 클래스 내부에 선언했다.

 

// eslint-disable-next-line
onCheckboxChange = (e: any) => {
    const list: Array<string> = this.state.checkList;
    list.push(e.target.value);
    this.setState({
        checkList: list,
    });
};

 

반응형

 

 

그리고 현재 글쓰기 모드가 수정 모드인지 신규 작성 모드인지 확인하기 위해서 isModifyMode 상태 값을 관리해야 하는데..

현재 내 상황은

App.tsx(부모)에 BoardList.tsx, Write.tsx가 있고, 아래와 같은 프로세스로 진행하려고 했다.

BoardList.tsx에서 수정 버튼 클릭 > Write.tsx에서 수정/신규 모드 확인

 

즉, BoardList에서 보낸 특정 상태 값을 Write에서 받을 수 있어야 한다.

 

자식 <> 부모 관계에서 props를 통해 데이터를 주고받을 수 있는 것 같아서 아래와 같이 구현하고자 한다.

 

a) BoardList.tsx에서 App.tsx로 수정 버튼을 눌렀다는 이벤트 전달

b) App.tsx에 수정 버튼 클릭 이벤트가 들어오면 Write.tsx에 수정 모드로 전환

c) Write.tsx에서 글 작성이 완료되면 App.tsx에 완료 이벤트 전달

d) App.tsx에 글 작성 완료 이벤트가 들어오면 BoardList.tsx에서 목록 리 렌더링

 

그러기 위해 각 소스 파일을 수정하였고, 수정된 내용이 많아서 전체 소스코드를 첨부하고, 설명은 간단히 한다.

 

BoardList.tsx

체크 박스 관련된 내용은 위에서 설명했으니, 그 외 나머지만 설명하겠다.

내용 1) 부모 state 변경상태 확인을 위해 componentDidUpdate를 추가하여, 글 작성/수정 완료 여부가 변경되면 목록을 리 렌더링 한다.

내용 2) 수정 버튼 클릭 시 현재 수정 모드임을 부모에게 전달하기 위해 부모에게 있는 handleModify 메서드를 호출해주고, 현재 선택된 게시글을 보내주었다.

 

import { Component } from "react";
import Axios from "axios";
import Table from "react-bootstrap/Table";
import Button from "react-bootstrap/Button";

const Board = ({
    id,
    title,
    registerId,
    registerDate,
    props,
}: {
    id: number;
    title: string;
    registerId: string;
    registerDate: string;
    props: any;
}) => {
    return (
        <tr>
            <td>
                <input
                    type="checkbox"
                    value={id}
                    onChange={(e) => {
                        props.onCheckboxChange(e.currentTarget.checked, e.currentTarget.value);
                    }}
                ></input>
            </td>
            <td>{id}</td>
            <td>{title}</td>
            <td>{registerId}</td>
            <td>{registerDate}</td>
        </tr>
    );
};

interface IProps {
    isComplete: boolean;
    handleModify: any;
    renderComplete: any;
}

/**
 * BoardList class
 * @param {SS} e
 */
class BoardList extends Component<IProps> {
    /**
     * @param {SS} props
     */
    constructor(props: any) {
        super(props);
        this.state = {
            boardList: [],
            checkList: [],
        };
    }

    state = {
        boardList: [],
        checkList: [],
    };

    getList = () => {
        Axios.get("http://localhost:8000/list", {})
            .then((res) => {
                const { data } = res;
                this.setState({
                    boardList: data,
                });
                this.props.renderComplete();
            })
            .catch((e) => {
                console.error(e);
            });
    };

    /**
     * @param {boolean} checked
     * @param {any} id
     */
    onCheckboxChange = (checked: boolean, id: any) => {
        const list: Array<string> = this.state.checkList.filter((v) => {
            return v != id;
        });

        if (checked) {
            list.push(id);
        }

        this.setState({
            checkList: list,
        });
    };

    /**
     */
    componentDidMount() {
        this.getList();
    }

    /**
     */
    componentDidUpdate() {
        if (!this.props.isComplete) {
            this.getList();
        }
    }

    /**
     * @return {Component} Component
     */
    render() {
        const { boardList }: { boardList: any } = this.state;

        return (
            <div>
                <Table striped bordered hover>
                    <thead>
                        <tr>
                            <th>선택</th>
                            <th>번호</th>
                            <th>제목</th>
                            <th>작성자</th>
                            <th>작성일</th>
                        </tr>
                    </thead>
                    <tbody>
                        {boardList.map((v: any) => {
                            return (
                                <Board
                                    id={v.BOARD_ID}
                                    title={v.BOARD_TITLE}
                                    registerId={v.REGISTER_ID}
                                    registerDate={v.REGISTER_DATE}
                                    key={v.BOARD_ID}
                                    props={this}
                                />
                            );
                        })}
                    </tbody>
                </Table>
                <Button variant="info">글쓰기</Button>
                <Button
                    variant="secondary"
                    onClick={() => {
                        this.props.handleModify(this.state.checkList);
                    }}
                >
                    수정하기
                </Button>
                <Button variant="danger">삭제하기</Button>
            </div>
        );
    }
}

export default BoardList;

 

 

 

Write.tsx

내용 1) 부모 state 변경상태 확인을 위해 componentDidUpdate를 추가하여, 수정 버튼을 눌렀는지 여부를 확인해서 상세 내용을 렌더링 한다.

내용 2) 작성 완료 버튼 클릭 시 수정 모드 여부에 따라 신규 작성/수정을 하고 완료 여부를 부모에게 전달한다.

내용 3) 취소 버튼 클릭 시 부모의 handleCancel 메서드를 호출하여 상태 값을 초기화한다.

 

import { Component } from "react";
import Axios from "axios";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";

interface IProps {
    isModifyMode: boolean;
    boardId: number;
    handleCancel: any;
}

/**
 * Write class
 * @param {SS} e
 */
class Write extends Component<IProps> {
    /**
     * @param {SS} props
     */
    constructor(props: any) {
        super(props);
        this.state = {
            title: "",
            content: "",
            isRendered: false,
        };
    }

    state = {
        title: "",
        content: "",
        isRendered: false,
    };

    write = () => {
        Axios.post("http://localhost:8000/insert", {
            title: this.state.title,
            content: this.state.content,
        })
            .then((res) => {
                this.setState({
                    title: "",
                    content: "",
                });
                this.props.handleCancel();
            })
            .catch((e) => {
                console.error(e);
            });
    };

    update = () => {
        Axios.post("http://localhost:8000/update", {
            title: this.state.title,
            content: this.state.content,
            id: this.props.boardId,
        })
            .then((res) => {
                this.setState({
                    title: "",
                    content: "",
                });
                this.props.handleCancel();
            })
            .catch((e) => {
                console.error(e);
            });
    };

    detail = () => {
        Axios.get(`http://localhost:8000/detail?id=${this.props.boardId}`)
            .then((res) => {
                if (res.data.length > 0) {
                    this.setState({
                        title: res.data[0].BOARD_TITLE,
                        content: res.data[0].BOARD_CONTENT,
                    });
                }
            })
            .catch((e) => {
                console.error(e);
            });
    };

    handleChange = (e: any) => {
        this.setState({
            [e.target.name]: e.target.value,
        });
    };

    /**
     *
     * @param {any} prevProps
     */
    componentDidUpdate = (prevProps: any) => {
        if (this.props.isModifyMode && this.props.boardId != prevProps.boardId) {
            this.detail();
        }
    };

    /**
     * @return {Component} Component
     */
    render() {
        return (
            <div>
                <Form>
                    <Form.Group className="mb-3">
                        <Form.Label>제목</Form.Label>
                        <Form.Control
                            type="text"
                            name="title"
                            value={this.state.title}
                            onChange={this.handleChange}
                            placeholder="제목을 입력하세요"
                        />
                    </Form.Group>
                    <Form.Group className="mb-3">
                        <Form.Label>내용</Form.Label>
                        <Form.Control
                            as="textarea"
                            name="content"
                            value={this.state.content}
                            onChange={this.handleChange}
                            placeholder="내용을 입력하세요"
                        />
                    </Form.Group>
                </Form>
                <Button variant="info" onClick={this.props.isModifyMode ? this.update : this.write}>
                    작성완료
                </Button>
                <Button variant="secondary" onClick={this.props.handleCancel}>
                    취소
                </Button>
            </div>
        );
    }
}

export default Write;
 

 

 

App.tsx

내용 1) 목록 화면과 글 작성 화면에서 공통으로 사용해야 하는 state값을 받아서 관리한다.

 

import { Component } from "react";
import "./App.css";
import "bootstrap/dist/css/bootstrap.min.css";
import BoardList from "BoardList";
import Write from "Write";

/**
 * App class
 */
class App extends Component {
    state = {
        isModifyMode: false,
        isComplete: true,
        boardId: 0,
    };

    /**
     * @param {any} checkList
     */
    handleModify = (checkList: any) => {
        if (checkList.length == 0) {
            alert("수정할 게시글을 선택하세요.");
        } else if (checkList.length > 1) {
            alert("하나의 게시글만 선택하세요.");
        }

        this.setState({
            isModifyMode: checkList.length == 1,
        });

        this.setState({
            boardId: checkList[0] || 0,
        });
    };

    handleCancel = () => {
        this.setState({
            isModifyMode: false,
            isComplete: false,
            boardId: 0,
        });
    };

    renderComplete = () => {
        this.setState({
            isComplete: true,
        });
    };

    /**
     * @return {Component} Component
     */
    render() {
        return (
            <div className="App">
                <BoardList
                    isComplete={this.state.isComplete}
                    handleModify={this.handleModify}
                    renderComplete={this.renderComplete}
                ></BoardList>
                <Write
                    isModifyMode={this.state.isModifyMode}
                    boardId={this.state.boardId}
                    handleCancel={this.handleCancel}
                ></Write>
            </div>
        );
    }
}

export default App;

 

 

현재까지 내용 정리

a) 작성 완료 버튼 event 추가(지난 포스팅)

b) 제목과 내용을 DB에 insert/update 하는 서버 API 코드 작성(지난 포스팅)

c) 수정하기 버튼 event 추가(이번 포스팅)

d) 게시글 상세 조회 select 하는 서버 API 코드 작성(이번 포스팅)

e) 삭제하기 버튼 event 추가

f) 게시글 삭제 delete 하는 서버 API 코드 작성

 

삭제는 다음 포스팅에서 해야겠다.

 

생각보다 react 상태 관리가 쉽지 않아서 오류도 많이 나고 그 부분들을 처리하느라 시간이 오래 걸렸다.

무엇보다 원래는 화면이 나뉘어 있어야 할 것들을 한 곳에 모아놔서.. 쓸데없는 상태 관리도 있다.

가장 부족한 건 내 실력이지만

 

 

포스팅하다 발생한 오류 사항

첫 번째

 

Type '{}' is not assignable to type 'IntrinsicAttributes & IntrinsicClassAttributes<> & Readonly<{}> & Readonly<{ children?: ReactNode; }>'.
Property '' does not exist on type 'IntrinsicAttributes & IntrinsicClassAttributes<> & Readonly<{}> & Readonly<{ children?: ReactNode; }>'

 

위와 비슷한 오류가 발생하신 분들은 아래 포스팅을 참고하면 된다.

 

https://artistjay.tistory.com/33

 

 

두 번째

 

Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.

 

위와 비슷한 오류가 발생하신 분들은 아래 포스팅을 참고하면 된다.

 

https://artistjay.tistory.com/34

반응형