본문 바로가기
ICIA 수업일지

2021.09.17 수업일지(React.js을 이용한 블로그 개설)

by 주성씨 2021. 9. 17.

- 어제에 이어서 게시글 목록 페이지를 만들도록 하겠다.

- 헤더, 포스트 리스트, 페이지 컴포넌트를 만들도록 하겠다.

 

1. 헤더

- C:\data\react\blog\client\src\components\(new)Header.js

import React from 'react';
import { Link } from 'react-router-dom';
import '../css/Header.css';

const Header = () => {
    return (
        <div className="headerBlock">
            <div className="logo">
                <Link to="/">REACTERS</Link>
            </div>
            <div className="right">
                <button className="grayButton">로그아웃</button>
            </div>
        </div>
    );
};

export default Header;

 

- C:\data\react\blog\client\src\css\(new)Header.css

.headerBlock {
    background: yellow;
    overflow: hidden;
    width: 100%;
    background: white;
    box-shadow: 5px 5px 5px gray;
    padding: 1rem;
}

.logo {
    float: left;
    font-size: 1.125rem;
    font-weight: 800;
    letter-spacing: 2px;
}

.right {
    margin-right: 1rem;
    float: right;
}

.right .userinfo {
    margin-right: 1rem;
    font-weight: 800;
}

 

 

1-1. 포스트 리스트

- C:\data\react\blog\client\src\components\PostList.js

import React from 'react';

const PostList = () => {
    return (
        <div>
            
        </div>
    );
};

export default PostList;

 

1-2. 헤더와 포스트 리스트를 담을 페이지 생성

- C:\data\react\blog\client\src\pages\PostListPage.js

import React from 'react';
import Header from '../components/Header';
import PostList from '../components/PostList';

const PostListPage = () => {
    return (
        <div>
            <Header/>
            <PostList/>
        </div>
    );
};

export default PostListPage;

 

1-3. App에 포스트리스트페이지를 Route 하겠다.

- C:\data\react\blog\client\src\App.js

import './App.css';
import { Route } from 'react-router-dom'
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import PostListPage from './pages/PostListPage';

function App() {
  return (
    <div className="App">
        <Route path="/" component={PostListPage}/>
        <Route path="/login" component={LoginPage}/>
        <Route path="/register" component={RegisterPage}/>
    </div>
  );
}

export default App;

 

- C:\data\react\blog\client\src\App.css

.App {
  width: 1024px;
  margin: 0 auto;
}

@media screen and (max-width: 1024px) {
  .App {
    width: 768px;
  }
}

@media screen and (max-width: 768px) {
  .App {
    width: 100%;
  }
}

.grayButton {
  border: none;
  border-radius: 4px;
  font-size: 0.9rem;
  font-weight: bold;
  padding: 0.4rem 1rem;
  color: white;
  background: #343a40;
  cursor: pointer;
  margin-right: 1rem;
}

.grayButton:hover {
  background: #adb5db;
}

.skyButton {
  border: none;
  border-radius: 4px;
  font-size: 0.9rem;
  font-weight: bold;
  padding: 0.4rem 1rem;
  color: white;
  background: #22b8cf;
  cursor: pointer;
  margin-right: 1rem;
}

.skyButton:hover {
  background: #3bc9db;
}

 

확인

 

1-4. 로그인한 정보를 세션에 넣어줄 수 있도록 하겠다.

서버에서도 할 수 있지만 클라이언트 쪽에서 작업하겠다.

- C:\data\react\blog\client\src\components\Auth.js

....

const Auth = () => {
....

    const onSubmit = (e) =>{
....
        
        axios.get(`/auth/${userid}`)
            .then(res => {
            if(res.data.length===1){
                if(res.data[0].password === password){
                    setError('로그인 성공');
                    alert('로그인 되었습니다.')
                    sessionStorage.setItem('userid', res.data[0].userid);
                    window.location.replace('/')
                }else{
                    setError('비밀번호가 일치하지 않습니다.')
                }
            }else{
                setError('해당 아이디가 존재하지 않습니다.')
            }
        });
    }

    return (
        <div className="authBlock">
....
        </div>
    );
};

export default Auth;

 

- C:\data\react\blog\client\src\components\Header.js

import React, { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
import '../css/Header.css';

const Header = () => {
    // userid store state var
    const [userid,setUserid] = useState('');

    useEffect(()=>{
        setUserid(sessionStorage.getItem('userid'));
    },[userid])

    const onLogout = () => {
        sessionStorage.removeItem('userid');
        setUserid('');
    }

    return (
        <div className="headerBlock">
            <div className="logo">
                <Link to ='/'>REACTERS</Link>
            </div>
            {userid ? (
                <div className="right">
                    <div>
                        <span className="userinfo">{userid}</span>
                        <button className="grayButton" onClick={onLogout}>로그아웃</button>
                    </div>
                </div>
                    ):(
                <div className="right">
                    <Link to="/login">
                        <button className="grayButton">로그인</button>
                    </Link>
                </div>
            )}
     </div>
    );
};

export default Header;

 

https://wooogy-egg.tistory.com/49

 

리액트 - history.push() 이후 refresh 안되는 문제

https://binaryjourney.tistory.com/15?category=916264 문제 현재 윗글 블로거 님의 글을 보면서 내가 개발하려는 서비스에 맞춰서 진행을 하고 있었다. (현재 개발하고 있는 서비스는 게시판 서비스와 동일하

wooogy-egg.tistory.com

 

확인

 

2. 게시글 목록을 출력하는 포스트 리스트를 만들어보자.

- sql문을 수정

- C:\data\react\blog\routes\posts.js

const express = require('express');
const router = express.Router();
const db = require('../server');

//게시글목록
router.get('/', (req, res) => {
    var sql = 'select * from tbl_post oreder by id desc';
    db.con.query(sql, (err, rows) => {
        res.send(rows);
    })
})

module.exports = router;

 

- C:\data\react\blog\client\src\components\PostList.js

import axios from 'axios';
import React, { useEffect, useState } from 'react';

const PostList = () => {
    const [posts,setPosts] = useState([]);
    const [loading,setLoading] = useState(false);

    const callAPI = async() => {
        setLoading(true);
        try {
            const res = await axios.get('/post/');
            setPosts(res.data);
            setLoading(false);
        } catch (error) {
            console.log(error);
            setLoading(false);
        }
    }

    useEffect(()=>{
        callAPI();
    },[]);
    
    if(loading){
        return <div className="postListBlock">Loading...</div>
    }

    return (
        <div className="postListBlock">
            {posts ? <textarea value={JSON.stringify(posts,null,2)}/>:''}
        </div>
    );
};

export default PostList;

 

확인

 

- 데이터에 CSS를 주도록 하겠다.

- C:\data\react\blog\client\src\components\PostList.js

import axios from 'axios';
import React, { useEffect, useState } from 'react';
import '../css/PostList.css';
// 내부 컴포넌트
const PostItem = ({post}) => {
    const {id,userid,title,body,createDate, fcreateDate} = post;
    return(
        <div className="postItemBlock">
            <h2>{title}</h2>
            <div className="subInfo">
                <span><b>{userid}</b></span>
                <span>{fcreateDate}</span>
            </div>
            <p>
                {body}
            </p>
        </div>
    )
}

const PostList = () => {
    const [posts,setPosts] = useState([]);
    const [loading,setLoading] = useState(false);

    const callAPI = async() => {
        setLoading(true);
        try {
            const res = await axios.get('/post/');
            setPosts(res.data);
            setLoading(false);
        } catch (error) {
            console.log(error);
            setLoading(false);
        }
    }

    useEffect(()=>{
        callAPI();
    },[]);
    
    if(loading){
        return <div className="postListBlock">Loading...</div>
    }

    return (
        <div className="postListBlock">
            {posts ? posts.map(post=><PostItem key={post.id} post={post}/>):''}
        </div>
    );
};

export default PostList;

 

- C:\data\react\blog\client\src\css\PostList.css

.postListBlock {
    margin-top: 1rem;
    overflow: hidden;
}

.buttonWrapper {
    overflow: hidden;
    margin-bottom: 1rem;
    text-align: right;
}

.postItemBlock {
    padding: 2rem;
    border-bottom: 1px solid #e2e2e2;
}

.postItemBlock .subInfo {
    margin-top: 0.5rem;
    color: #8a8585;
}

.postItemBlock h2 {
    font-size: 2rem;
    margin-top: 1rem;
    margin-bottom: 0;
}

.postItemBlock h2 a {
    color: gray;
}

.postItemBlock h2 a:hover {
    color: #bebebe;
}

.postItemBlock p {
    margin-top: 2rem;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
}

.subInfo span {
    padding-left: 0.25rem;
    padding-right: 0.25rem;
}

 

- 날자포맷수정

- C:\data\react\blog\routes\posts.js

const express = require('express');
const router = express.Router();
const db = require('../server');

//게시글목록
router.get('/', (req, res) => {
    var sql = 'select *,date_format(createDate,"%Y-%m-%d %H:%i:%s") fcreateDate from tbl_post order by id desc';
    db.con.query(sql, (err, rows) => {
        res.send(rows);
    })
})

module.exports = router;

 

https://www.w3schools.com/mysql/func_mysql_date_format.asp

 

MySQL DATE_FORMAT() Function

MySQL DATE_FORMAT() Function ❮ MySQL Functions Definition and Usage The DATE_FORMAT() function formats a date as specified. Syntax DATE_FORMAT(date, format) Parameter Values Parameter Description date Required. The date to be formatted format Required. T

www.w3schools.com

 

확인

 

- 페이지 이동 컴포넌트를 만들겠다.

- 일단 데이터 단순 자가복제 insert 작업을 하겠다.

- Mysql

#2021.09.17
insert into tbl_post(userid,title,body)
select userid, title, body from tbl_post;

#확인
select count(*) from tbl_post;

 

- routes sql문 수정

- C:\data\react\blog\routes\posts.js

const express = require('express');
const router = express.Router();
const db = require('../server');

//게시글목록
router.get('/', (req, res) => {
    var sql = 'select *,date_format(createDate,"%Y-%m-%d %H:%i:%s") fcreateDate';
    sql += ' from tbl_post order by id desc limit 0,2';
    db.con.query(sql, (err, rows) => {
        res.send(rows);
    })
})

module.exports = router;

 

- pagination component를 만들겠다.

- C:\data\react\blog\client\src\components\(new)Pagination.js

import React from 'react';
import '../css/Pagination.css'

const Pagination = () => {
    return (
        <div className="paginationBlock">
            <button className="grayButton">&lt;</button>
            <span>1/96</span>
            <button className="grayButton">&gt;</button>
        </div>
    );
};

export default Pagination;

 

- C:\data\react\blog\client\src\css\(new)Pagination.css

.paginationBlock {
    width: 320px;
    margin: 0 auto;
    display: flex;
    justify-content: space-between;
    margin-bottom: 3rem;
    margin-top: 1rem;
}

.paginationBlock button:disabled {
    background: #adb5db;
}

 

확인

 

 

- 페이지를 변경하면 출력값도 바뀌게 하겠다.

- C:\data\react\blog\routes\posts.js

const express = require('express');
const router = express.Router();
const db = require('../server');

// 1. 게시글 목록
router.get('/', (req, res) => {
    var page = parseInt(req.query.page);
    var start = (page-1)*2
    var sql = 'select *,date_format(createDate,"%Y-%m-%d %H:%i:%s") fcreateDate';
    sql += ' from tbl_post order by id desc limit ?,2';
    db.con.query(sql,[start] ,(err, rows) => {
        res.send(rows);
    })
})

module.exports = router;

 

- 쿼리 문자열을 객체로 변환을 위한 qs라는 라이브러리를 아래와 같이 설치한다.

// 클라이언트 진입
C:\data\react\blog>cd client

// qs 라이브러리 설치
C:\data\react\blog\client>yarn add qs

 

- C:\data\react\blog\client\src\components\PostList.js

import axios from 'axios';
import React, { useEffect, useState } from 'react';
import '../css/PostList.css';
import Pagination from './Pagination';
import {withRouter} from 'react-router-dom';
import qs from 'qs';

// 내부 컴포넌트
const PostItem = ({post}) => {
    const {id,userid,title,body,fcreateDate} = post;
    return(
        <div className="postItemBlock">
            <h2>{id} : {title}</h2>
            <div className="subInfo">
                <span><b>{userid}</b></span>
                <span>{fcreateDate}</span>
            </div>
            <p>
                {body}
            </p>
        </div>
    )
}

const PostList = ({location}) => {
    const [posts,setPosts] = useState([]);
    const [loading,setLoading] = useState(false);

    const query = qs.parse(location.search, {ignoreQueryPrefix:true})
    const page = !query.page ? 1 : query.page;

    const callAPI = async() => {
        setLoading(true);
        try {
            const res = await axios.get(`/post?page=${page}`);
            setPosts(res.data);
            setLoading(false);
        } catch (error) {
            console.log(error);
            setLoading(false);
        }
    }

    useEffect(()=>{
        callAPI();
    },[page]);
    
    if(loading){
        return <div className="postListBlock">Loading...</div>
    }

    return (
        <div className="postListBlock">
            {posts ? posts.map(post=><PostItem key={post.id} post={post}/>):''}
            <Pagination page={page}/>
        </div>
    );
};

export default withRouter(PostList);

 

값을 가지고 오는 간단한 과정

 

- C:\data\react\blog\client\src\components\Pagination.js

import '../css/Pagination.css'
import { Link } from 'react-router-dom';
import React from 'react';

const Pagination = ({page}) => {
    var intPage = parseInt(page);
    return (
        <div className="paginationBlock">
            <Link to={`/?page=${intPage-1}`}>
                <button className="grayButton">&lt;</button>
            </Link>
            <span>{page}/96</span>
            <Link to={`/?page=${intPage+1}`}>
                <button className="grayButton">&gt;</button>
            </Link>
        </div>
    );
};

export default Pagination;

 

확인

 

 

- 첫페이지 마지막 페이지에서 버튼이 disabled하게 하겠다.

- C:\data\react\blog\routes\posts.js

// 2. 게시글의 전체 갯수를 구하는 메서드
router.get('/count',(req,res)=>{
    var sql = "select count(*) total from tbl_post";
    db.con.query(sql,(err,rows)=>{
        res.send(rows[0]);
    })
})

 

- C:\data\react\blog\client\src\components\Pagination.js

import '../css/Pagination.css'
import { withRouter } from 'react-router-dom';
import React from 'react';

const Pagination = ({page, lastPage,history}) => {
    var intPage = parseInt(page);
    var intLastPage = parseInt(lastPage);

    const onNext=()=>{
        history.push(`/?page=${intPage+1}`)
    }
    const onPrev=()=>{
        history.push(`/?page=${intPage-1}`)
    }

    return (
        <div className="paginationBlock">
            <button className="grayButton" disabled={intPage===1} onClick={onPrev}>&lt;</button>
            <span>{page}/{lastPage}</span>
            <button className="grayButton" disabled={intPage===intLastPage} onClick={onNext}>&gt;</button>
        </div>
    );
};

export default withRouter(Pagination);

 

확인

 

 

- 새글 작성하기 버튼을 만들고 새글까지 작성하는 작업을 해보겠다.

- C:\data\react\blog\client\src\components\PostList.js

    return (
        <div className="postListBlock">
            <div className="buttonWrapper">
                <button className="skyButton">새글 작성하기</button>
            </div>
            {posts ? posts.map(post=><PostItem key={post.id} post={post}/>):''}
            <Pagination page={page} lastPage={lastPage}/>
        </div>
    );

 

확인

 

- C:\data\react\blog\client\src\components\PostList.js

import axios from 'axios';
import React, { useEffect, useState } from 'react';
import '../css/PostList.css';
import Pagination from './Pagination';
import {Link, withRouter} from 'react-router-dom';
import qs from 'qs';

// 내부 컴포넌트
const PostItem = ({post}) => {
    const {id,userid,title,body,fcreateDate} = post;
    return(
        <div className="postItemBlock">
            <h2>{id} : {title}</h2>
            <div className="subInfo">
                <span><b>{userid}</b></span>
                <span>{fcreateDate}</span>
            </div>
            <p>
                {body}
            </p>
        </div>
    )
}

const PostList = ({location}) => {
    const [posts,setPosts] = useState([]);
    const [loading,setLoading] = useState(false);

    const [lastPage,setLastPage] = useState(1);

    const [userid,setUserid] = useState('');

    const query = qs.parse(location.search, {ignoreQueryPrefix:true})
    const page = !query.page ? 1 : query.page;

    const callAPI = async() => {
        setLoading(true);
        try {
            var res = await axios.get(`/post?page=${page}`);
            setPosts(res.data);
            res = await axios.get(`/post/count`);
            setLastPage(Math.ceil(parseInt(res.data.total)/2));
            setLoading(false);
        } catch (error) {
            console.log(error);
            setLoading(false);
        }
    }

    useEffect(()=>{
        callAPI();
        setUserid(sessionStorage.getItem('userid'));
    },[page]);
    
    if(loading){
        return <div className="postListBlock">Loading...</div>
    }

    return (
        <div className="postListBlock">
            <div className="buttonWrapper">
                <Link to={userid ? '/wirte':'/login'}>
                    <button className="skyButton">새글 작성하기</button>
                </Link>
            </div>
            {posts ? posts.map(post=><PostItem key={post.id} post={post}/>):''}
            <Pagination page={page} lastPage={lastPage}/>
        </div>
    );
};

export default withRouter(PostList);

 

- C:\data\react\blog\client\src\components\(new)Write.js

import React from 'react';

const Write = () => {
    return (
        <div>
            <h1>글쓰기</h1>
        </div>
    );
};

export default Write;

 

- C:\data\react\blog\client\src\pages\(new)WritePage.js

import React from 'react';
import Header from '../component/Header.js'
import Write from '../components/Write.js';
const WritePage = () => {
    return (
        <div>
            <Header/>
            <Write/>
        </div>
    );
};

export default WritePage;

 

- 만든 라우트를 app.js에서 등록

- C:\data\react\blog\client\src\App.js

import './App.css';
import { Route } from 'react-router-dom'
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import PostListPage from './pages/PostListPage';
import WritePage from './pages/WritePage';

function App() {
  return (
    <div className="App">
        <Route path="/" component={PostListPage} exact={true}/>
        <Route path="/login" component={LoginPage}/>
        <Route path="/register" component={RegisterPage}/>
        <Route path="/wirte" component={WritePage}/>
    </div>
  );
}

export default App;

 

- 로그아웃시 초기화면으로

- C:\data\react\blog\client\src\components\Header.js

....
	const onLogout = () => {
        sessionStorage.removeItem('userid');
        setUserid('');
        window.location.replace('/');
    }
....

 

- 글쓰기 html 꾸미기

- C:\data\react\blog\client\src\components\Write.js

import React from 'react';
import '../css/Write.css';

const Write = () => {
    return (
        <div className="postWriteBlock">
            <form className="postForm">
                <input type="text" placeholder="제목을 입력하세요."/>
                <textarea placeholder="내용을 입력하세요."></textarea>
                <div className="postButtonWrapper">
                    <button className="skyButton">포스트 등록</button>
                    <button className="skyButton">취소</button>
                </div>
            </form>
        </div>
    );
};

export default Write;

 

확인

 

 

- title, body, userid를 이용해 게시글을 입력하여 db에 입력할 수 있도록 하겠다.

- C:\data\react\blog\client\src\components\Write.js

import React, { useState } from 'react';
import '../css/Write.css';

const Write = () => {

    const[form,setForm] = useState({
        userid: sessionStorage.getItem('userid'),
        title:'',
        body:''
    });

    const {userid, title, body} = form;

    const onChange =(e)=>{
        setForm({
            ...form,
            [e.target.name]:e.target.value
        })
    }

    const onSubmit = (e) => {
        e.preventDefault();
        if(title===''){
            alert('제목을 입력하세요.')
            return;
        }else if(body===''){
            alert('내용을 입력하세요.')
            return;
        }else{
            if(!window.confirm('글을 등록하시겠습니까?')) return;
        }
    }

    return (
        <div className="postWriteBlock">
            <form className="postForm" onSubmit={onSubmit}>
                <input type="text" name="title" value={title} onChange={onChange} placeholder="제목을 입력하세요."/>
                <textarea placeholder="내용을 입력하세요." name="body" onChange={onChange}>{body}</textarea>
                <div className="postButtonWrapper">
                    <button type="submit" className="skyButton">포스트 등록</button>
                    <button type="reset" className="skyButton">취소</button>
                </div>
            </form>
        </div>
    );
};

export default Write;

 

- 게시글 등록 메서드를 생성하겠다.

- 우선 글이 잘 들어오는지 콘솔에 찍어보겠다.

- C:\data\react\blog\routes\posts.js

// 3. 게시글 등록
router.post('/insert',(req,res)=>{
    var userid = req.body.userid;
    var title = req.body.title;
    var body = req.body.body;

    console.log(userid, title, body);
    
    // var sql = "insert into tbl_post(userid,title,body) values(?,?,?)";
    // db.con.query(sql,[userid,title,body],(err,rows)=>{
    //     res.send(rows);
    // })
})

module.exports = router;

 

- C:\data\react\blog\client\src\components\Write.js

....
    const onSubmit = (e) => {
        e.preventDefault();
        if(title===''){
            alert('제목을 입력하세요.')
            return;
        }else if(body===''){
            alert('내용을 입력하세요.')
            return;
        }else{
            if(!window.confirm('글을 등록하시겠습니까?')) return;
        }
        axios.post('/post/insert',form).then(res=>{
            alert('글이 등록되었습니다.');
            window.location.replace('/')
        });
    }
....

 

확인

 

- 이제 라우트스에서 sql문을 이용해 데이터를 db에 넣어보겠다.

- C:\data\react\blog\routes\posts.js

// 3. 게시글 등록
router.post('/insert',(req,res)=>{
    var userid = req.body.userid;
    var title = req.body.title;
    var body = req.body.body;

    var sql = "insert into tbl_post(userid,title,body) values(?,?,?)";
    db.con.query(sql,[userid,title,body],(err,rows)=>{
        res.send(rows);
    })
})

 

확인

 

확인

 

 

- 이제 특정 게시글을 클릭하여 내용을 읽을 수 있도록 하겠다.

- 게시글을 읽는 라우터를 만들어 만들어보겠다.

- C:\data\react\blog\routes\posts.js

.....

// 4. 게시글 읽기
router.get('/view/:postid', (req, res) => {
    var id = req.params.postid
    var sql = 'select *,date_format(createDate,"%Y-%m-%d %H:%i:%s") fcreateDate from tbl_post where id=?';
    db.con.query(sql,[id] ,(err, rows) => {
        res.send(rows[0]);
    })
})

module.exports = router;

 

확인

 

- C:\data\react\blog\client\src\components\(new)Viewer.js

import React from 'react';
import '../css/Viewer.css'

const Viewer = () => {
    return (
        <div className="viewerWriteBlock">
            <form className="viewerForm">
                <input type="text" name="title"/>
                <div className="subInfo">
                    <span></span>
                    <span></span>
                </div>
                <textarea name="body"></textarea>
            </form>
        </div>
    );
};

export default Viewer;

 

- C:\data\react\blog\client\src\pages\(new)ViewerPage.js

import React from 'react';
import Viewer from '../components/Viewer';
import Header from '../components/Header';

const ViewerPage = () => {
    return (
        <div>
            <Header/>
            <Viewer/>
        </div>
    );
};

export default ViewerPage;

 

- C:\data\react\blog\client\src\App.js

import './App.css';
import { Route } from 'react-router-dom'
import LoginPage from './pages/LoginPage';
import RegisterPage from './pages/RegisterPage';
import PostListPage from './pages/PostListPage';
import WritePage from './pages/WritePage';
import ViewerPage from './pages/ViewerPage';

function App() {
  return (
    <div className="App">
        <Route path="/" component={PostListPage} exact={true}/>
        <Route path="/login" component={LoginPage}/>
        <Route path="/register" component={RegisterPage}/>
        <Route path="/wirte" component={WritePage}/>
        <Route path="/view/:postid" component={ViewerPage}/>
    </div>
  );
}

export default App;

 

확인

 

- 이제 viewer.js를 디자인해보자.

- C:\data\react\blog\client\src\css\(new)Viewer.css

.viewerWriteBlock {
    overflow: hidden;
}

.viewerForm {
    padding: 1rem;
}

.viewerForm input {
    width: 100%;
    font-size: 2rem;
    border: none;
    outline: none;
    margin-top: 3rem;
    padding-bottom: 1rem;
}

.viewerForm input:hover {
    border-bottom: 1px solid #495057;
}

.viewerForm textarea {
    font-family: inherit;
    width: 100%;
    font-size: 1.2rem;
    margin-top: 2rem;
    min-height: 300px;
    border: none;
    outline: none;
    border-bottom: 1px solid #e2e2e2;
}

.viewerForm textarea:hover {
    border-bottom: 1px solid #495057;
}

.viewerForm .subInfo {
    margin-top: 1rem;
    color: #495057;
    padding-bottom: 1rem;
    border-bottom: 1px solid #e2e2e2;
}

 

- 특정 게시글의 아이디를 이용해 /view/id 로 이동할 수 있도록 하겠다.

- C:\data\react\blog\client\src\components\PostList.js

.....
const PostItem = ({post}) => {
    const {id,userid,title,body,fcreateDate} = post;
    return(
        <div className="postItemBlock">
            <h2><Link to={`/view/${id}`}>{id} : {title}</Link></h2>
            <div className="subInfo">
                <span><b>{userid}</b></span>
                <span>{fcreateDate}</span>
            </div>
            <p>
                {body}
            </p>
        </div>
    )
}
.....

 

확인

 

 

- C:\data\react\blog\client\src\components\Viewer.js

import React, { useEffect, useState } from 'react';
import '../css/Viewer.css'
import { withRouter } from 'react-router';
import axios from 'axios';

const Viewer = ({match}) => {
    const {postid} = match.params;
    const [form,setForm] = useState({
        id:0,
        userid:'',
        title:'',
        body:'',
        fcreateDate:''
    })

    const{id,userid,title,body,fcreateDate} = form;

    const callAPI = async() =>{
        try {
            const res = await axios.get(`/post/view/${postid}`);
            setForm(res.data);
        } catch (error) {
            console.log(error);
        }
    }

    useEffect(()=>{
        callAPI();
    },[postid])

    return (
        <div className="viewerWriteBlock">
            <form className="viewerForm">
                <input type="text" name="title" value={title}/>
                <div className="subInfo">
                    <span><b>{userid}</b></span>
                    <span>{fcreateDate}</span>
                </div>
                <textarea name="body" value={body}></textarea>
            </form>
        </div>
    );
};

export default withRouter(Viewer);

 

확인

 

 

- 이제 업데이트 작업을 해보도록 하겠다.

- 수정, 삭제 컴포넌트를 따로 만들겠다.

 

- C:\data\react\blog\client\src\components\(new)Update.js

import React from 'react';
import '../css/Update.css'
const Update = () => {
    return (
        <div className="postUpdate">
            <span>수정</span>
            <span>삭제</span>
        </div>
    );
};

export default Update;

 

- C:\data\react\blog\client\src\css\(new)Update.css

.postUpdate {
    overflow: hidden;
    margin-top: 1rem;
    text-align: right;
}

.postUpdate span {
    font-size: 0.875rem;
    color: #495057;
    padding: 0.3rem;
    cursor: pointer;
}

.postUpdate span:hover {
    color: #e2e2e2;
}

 

- C:\data\react\blog\client\src\components\Viewer.js

....
import Update from './Update';

const Viewer = ({match}) => {
....
    const loginId = sessionStorage.getItem('userid');
....

....

    return (
        <div className="viewerWriteBlock">
            <form className="viewerForm">
                <input type="text" name="title" value={title}/>
                <div className="subInfo">
                    <span><b>{userid}</b></span>
                    <span>{fcreateDate}</span>
                </div>
                {userid===loginId && <Update/>}
                <textarea name="body" value={body}></textarea>
            </form>
        </div>
    );
};

export default withRouter(Viewer);

 

확인

 

 

- C:\data\react\blog\client\src\components\Viewer.js

....

const Viewer = ({match}) => {
....
....

....

....

....

    const onChange =(e)=>{
        setForm({
            ...form,
            [e.target.name]:e.target.value
        })
    }

    return (
        <div className="viewerWriteBlock">
            <form className="viewerForm">
                <input type="text" name="title" onChange={onChange} value={title} disabled={userid !== loginId}/>
                <div className="subInfo">
                    <span><b>{userid}</b></span>
                    <span>{fcreateDate}</span>
                </div>
                {userid===loginId && <Update/>}
                <textarea name="body" onChange={onChange} value={body} disabled={userid !== loginId}></textarea>
            </form>
        </div>
    );
};

export default withRouter(Viewer);

 

확인

 

- form 데이터를 Update.js에 넣어가지고 가도록 하겠다.

- C:\data\react\blog\client\src\components\Viewer.js

    return (
        <div className="viewerWriteBlock">
            <form className="viewerForm">
                <input type="text" name="title" onChange={onChange} value={title} disabled={userid !== loginId}/>
                <div className="subInfo">
                    <span><b>{userid}</b></span>
                    <span>{fcreateDate}</span>
                </div>
                {userid===loginId && <Update post={form}/>}
                <textarea name="body" onChange={onChange} value={body} disabled={userid !== loginId}></textarea>
            </form>
        </div>
    );

 

- C:\data\react\blog\client\src\components\Update.js

import React from 'react';
import '../css/Update.css'
const Update = ({post}) => {
    const { id, title, body } = post;
    const onUpdate = (e) => {
        e.preventDefault();
        if(!window.confirm('글을 수정하시겠습니까?')) return;
        
    }
    const onDelete = (e) => {
        e.preventDefault();
        if(!window.confirm('글을 삭제하시겠습니까?')) return;

    }
    return (
        <div className="postUpdate">
            <span onClick={onUpdate}>수정</span>
            <span onClick={onDelete}>삭제</span>
        </div>
    );
};

export default Update;

 

확인

 

 

- 이제 글 삭제 수정하는 라우터를 만들도록 하겠다.

- C:\data\react\blog\routes\posts.js

.....
// 5. 게시글 삭제
router.post('/delete',(req,res)=>{
    var id = req.body.id
    var sql = "delete from tbl_post where id=?";
    db.con.query(sql,[id],(err,rows)=>{
        res.send(rows);
    })
})

// 6. 게시글 수정
router.post('/update',(req,res)=>{
    var id = req.body.id;
    var title = req.body.title;
    var body = req.body.body;

    var sql = "update tbl_post set title=?,body=? where id=?";
    db.con.query(sql,[title,body,id],(err,rows)=>{
        res.send(rows);
    })
})

module.exports = router;

 

- C:\data\react\blog\client\src\components\Update.js

import axios from 'axios';
import React from 'react';
import '../css/Update.css'
const Update = ({post}) => {
    const { id, title, body } = post;
    const onUpdate = (e) => {
        e.preventDefault();
        if(!window.confirm('글을 수정하시겠습니까?')) return;
        axios.post('/post/update',post).then(res=>{
            alert('수정되었습니다.');
            window.location.replace('/');
        })
        
    }
    const onDelete = (e) => {
        e.preventDefault();
        if(!window.confirm('글을 삭제하시겠습니까?')) return;
        axios.post('/post/delete',post).then(res=>{
            alert('삭제되었습니다.');
            window.location.replace('/')
        })
    }
    return (
        <div className="postUpdate">
            <span onClick={onUpdate}>수정</span>
            <span onClick={onDelete}>삭제</span>
        </div>
    );
};

export default Update;

 

확인

 

- 검색하는 작업을 해보겠다.