목표

  • 수정 페이지 만들기
  • 게시글 삭제 버튼 만들기
  • axios.patch()로 서버 데이터 수정하기
  • axios.delete()로 서버 데이터 삭제하기

 


결과 화면

 


1. state 변수 만들기

  • src/pages/board/modify/index.tsx

    1. title(타이틀)과 content(콘텐츠) state 변수를 만들고,

    Input, Textarea 컴포넌트의 onChange 속성에 state 변수를 넣어 입력할 때마다 변하는 입력값을 state 변수에 저장한다.

 

    2. axios.get()으로 서버 데이터를 받아오고,

    수정 페이지는 기존에 등록했던 title과 content의 값을 수정해야 하기 때문에 [modifyBoardData] state 변수를 만들어 초기값을 설정한다.

 

    3. 페이지가 로딩되자마자 데이터들이 보여야 하기 때문에 useEffect()로 [modifyBoardData] state 변수에 초기값을 설정한다.

const BoardModify = () => {
  // hook
  const params = useParams().id
  const navigate = useNavigate()

  // state
  let [modifyBoardData, setModifyBoardData] = useState<any>([])
  let [title, setTitle] = useState<string>(modifyBoardData.title)
  let [content, setContent] = useState<string>(modifyBoardData.content)

  useEffect(() => {
    axios.get(`http://localhost:3001/board/${params}`)
      .then((response) => {
        setModifyBoardData(response.data)
      })

      .catch(function(error) {
        navigate('/error/404')
        console.log(error)
      })
  }, [])

  useEffect(() => {
    setTitle(modifyBoardData.title)
  }, [modifyBoardData.title])

  useEffect(() => {
    setContent(modifyBoardData.content)
  }, [modifyBoardData.content])

  return (
    <div className="board-modify">
      <Title children="Write content"/>

      <form>
        <div>
          <Label htmlFor="title">Title</Label>
          <Input type="text" id="title" value={title || ""} onChange={(e) => setTitle(e.target.value)} placeholder="제목을 입력해주세요."/>
        </div>

        <div>
          <Label htmlFor="content">Content</Label>
          <Textarea name="content" id="content" value={content || ""} onChange={(e) => setContent(e.target.value)} placeholder="내용을 입력해주세요."/>
        </div>
      </form>

      <div className="grid-3">
        <Button children="Delete" variant="danger" onClick={formDelete}/>
        <Button children="Confirm" variant="primary" onClick={formSubmit}/>
        <Button children="Cancel" variant="secondary" onClick={formCancel}/>
      </div>
    </div>
  )
}

export default BoardModify

 

2. axios.patch()로 서버 데이터 수정하기

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

Postman을 통해서 PATCH로 수정한 데이터를 확인할 수 있다.

 

 

  • src/pages/board/modify/index.tsx
  1. 확인(Confirm) 버튼을 누를 때 데이터가 수정되어야 하기 때문에 formSubmit() 클릭 이벤트를 만든다.
  2. 빈 값을 전송하면 안 되기 때문에 if문을 통해 length가 0일 때 alert 창이 나오게 만든다.
  3. 수정 페이지는 서버에 데이터를 수정해야 하기 때문에 patch() 메서드를 사용한다.
  4. patch() 안에 전송할 데이터의 key와 value를 작성한다. (title, content, updated_at)
const BoardModify = () => {
  // hook
  const params = useParams().id
  const navigate = useNavigate()

  // state
  let [modifyBoardData, setModifyBoardData] = useState<any>([])
  let [title, setTitle] = useState<string>(modifyBoardData.title)
  let [content, setContent] = useState<string>(modifyBoardData.content)

  const formSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(title.length === 0) {
      alert('제목을 입력해 주세요.')
    } else if(content.length === 0) {
      alert('내용을 입력해 주세요.')
    } else {
      if(window.confirm('게시글을 수정하시겠습니까?')) {
        axios.patch(`http://localhost:3001/board/${params}`, {
          title: title,
          content: content,
          updated_at: new Date()
        })
          .then(function(response) {
            alert('게시글이 수정되었습니다.')
            navigate('/board')
          })

          .catch(function(error) {
            console.log(error)
          })
      } else {
        return false
      }
    }
  }

  ...

  return (
    <div className="board-modify">
      ...
    </div>
  )
}

export default BoardModify

 

3. axios.delete()로 서버 데이터 삭제하기

  • src/pages/board/modify/index.tsx
  1. 삭제(Cancel) 버튼을 누를 때 데이터가 삭제되어야 하기 때문에 formDelete() 클릭 이벤트를 만든다.
  2. 취소(Cancel) 버튼을 누르면 게시판 목록 페이지로 갈 수 있게 formCancel() 클릭 이벤트를 만든다.
const BoardModify = () => {
  ...
  
  const formDelete= (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(window.confirm('게시글을 삭제하시겠습니까?')) {
      axios.delete(`http://localhost:3001/board/${params}`, {
      })
        .then(function(response) {
          alert('게시글이 삭제되었습니다.')
          navigate('/board')
        })

        .catch(function(error) {
          console.log(error)
        })
    } else {
      return false
    }
  }

  const formCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(window.confirm('게시글 수정을 취소하시겠습니까?')) {
      navigate('/board')
    } else {
      return false
    }
  }

  ...

  return (
    <div className="board-modify">
      ...
    </div>
  )
}

export default BoardModify

 


전체 코드

  • src/pages/board/modify/index.tsx

FIXME:: state에 데이터를 저장할 때 타입을 any로 설정했다. 수정할 예정이기 때문에 타입에 주의한다.

import { useState, useEffect } from 'react'
import { useParams } from 'react-router'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import Button from '../../../components/form/Button'
import Input from '../../../components/form/Input'
import Label from '../../../components/form/Label'
import Textarea from '../../../components/form/Textarea'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardModify = () => {
  // hook
  const params = useParams().id
  const navigate = useNavigate()

  // state
  // TODO :: any 타입 수정
  let [modifyBoardData, setModifyBoardData] = useState<any>([])
  let [title, setTitle] = useState<string>(modifyBoardData.title)
  let [content, setContent] = useState<string>(modifyBoardData.content)

  const formSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(title.length === 0) {
      alert('제목을 입력해 주세요.')
    } else if(content.length === 0) {
      alert('내용을 입력해 주세요.')
    } else {
      if(window.confirm('게시글을 수정하시겠습니까?')) {
        axios.patch(`http://localhost:3001/board/${params}`, {
          title: title,
          content: content,
          updated_at: new Date()
        })
          .then(function(response) {
            alert('게시글이 수정되었습니다.')
            navigate('/board')
          })

          .catch(function(error) {
            console.log(error)
          })
      } else {
        return false
      }
    }
  }

  const formDelete= (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(window.confirm('게시글을 삭제하시겠습니까?')) {
      axios.delete(`http://localhost:3001/board/${params}`, {
      })
        .then(function(response) {
          alert('게시글이 삭제되었습니다.')
          navigate('/board')
        })

        .catch(function(error) {
          console.log(error)
        })
    } else {
      return false
    }
  }

  const formCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(window.confirm('게시글 수정을 취소하시겠습니까?')) {
      navigate('/board')
    } else {
      return false
    }
  }

  useEffect(() => {
    axios.get(`http://localhost:3001/board/${params}`)
      .then((response) => {
        setModifyBoardData(response.data)
      })

      .catch(function(error) {
        navigate('/error/404')
        console.log(error)
      })
  }, [])

  useEffect(() => {
    setTitle(modifyBoardData.title)
  }, [modifyBoardData.title])

  useEffect(() => {
    setContent(modifyBoardData.content)
  }, [modifyBoardData.content])

  return (
    <div className="board-modify">
      <Title children="Write content"/>

      <form>
        <div>
          <Label htmlFor="title">Title</Label>
          <Input type="text" id="title" value={title || ""} onChange={(e) => setTitle(e.target.value)} placeholder="제목을 입력해주세요."/>
        </div>

        <div>
          <Label htmlFor="content">Content</Label>
          <Textarea name="content" id="content" value={content || ""} onChange={(e) => setContent(e.target.value)} placeholder="내용을 입력해주세요."/>
        </div>
      </form>

      <div className="grid-3">
        <Button children="Delete" variant="danger" onClick={formDelete}/>
        <Button children="Confirm" variant="primary" onClick={formSubmit}/>
        <Button children="Cancel" variant="secondary" onClick={formCancel}/>
      </div>
    </div>
  )
}

export default BoardModify

 

#5
axios.patch()로 서버 데이터를 수정하고,
axios.delete()로 서버 데이터를 삭제했다.

목표

  • 생성 페이지 만들기
  • axios.post()로 서버에 데이터 전송하기

 


결과 화면

 


1. state 변수 만들기

  • src/pages/board/create/index.tsx

title(타이틀)과 content(콘텐츠) state 변수를 만들고,

Input, Textarea 컴포넌트의 onChange 속성에 state 변수를 넣어 입력할 때마다 변하는 입력값을 state 변수에 저장한다.

const BoardCreate = () => {
  // state
  let [title, setTitle] = useState<string>('')
  let [content, setContent] = useState<string>('')

  return (
    <div className="board-create">
      <Title children="Write content"/>

      <form>
        <div>
          <Label htmlFor="title">Title</Label>
          <Input type="text" id="title" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="제목을 입력해주세요."/>
        </div>

        <div>
          <Label htmlFor="content">Content</Label>
          <Textarea name="content" id="content" value={content} onChange={(e) => setContent(e.target.value)} placeholder="내용을 입력해주세요."/>
        </div>
      </form>

      <div className="grid-2">
        <Button children="Confirm" variant="primary" onClick={formSubmit}/>
        <Button children="Cancel" variant="secondary" onClick={formCancel}/>
      </div>
    </div>
  )
}

export default BoardCreate

 

2. axios.post로 서버에 데이터 보내기

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

Postman을 통해서 POST로 전송한 데이터를 확인할 수 있다.

 

 

  • src/pages/board/create/index.tsx
  1. 확인(Confirm) 버튼을 누를 때 데이터가 전송되기 때문에 formSubmit() 클릭 이벤트를 만든다.
  2. 빈 값을 전송하면 안되기 때문에 if문을 통해 length가 0일 때 alert 창이 나오게 만든다.
  3. 생성 페이지는 서버에 데이터를 전송해야 하기 때문에 post() 메서드를 사용한다.
  4. post() 안에 전송할 데이터의 key와 value를 작성한다. (title, content)
  5. 취소(Cancel) 버튼을 누르면 게시판 목록 페이지로 갈 수 있게 formCancel() 클릭 이벤트를 만든다.
import { useState } from 'react'
import { useNavigate } from 'react-router-dom'
import axios from 'axios'
import Button from '../../../components/form/Button'
import Input from '../../../components/form/Input'
import Label from '../../../components/form/Label'
import Textarea from '../../../components/form/Textarea'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardCreate = () => {
  // hook
  const navigate = useNavigate()

  // state
  let [title, setTitle] = useState<string>('')
  let [content, setContent] = useState<string>('')

  const formSubmit = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(title.length === 0) {
      alert('제목을 입력해 주세요.')
    } else if(content.length === 0) {
      alert('내용을 입력해 주세요.')
    } else {
      if(window.confirm('게시글을 등록하시겠습니까?')) {
        axios.post('http://localhost:3001/board', {
          title: title,
          content: content
        })
          .then(function(response) {
            alert('게시글이 등록되었습니다.')
            navigate('/board')
          })

          .catch(function(error) {
            console.log(error)
          })
      } else {
        return false
      }
    }
  }

  const formCancel = (e: React.MouseEvent<HTMLButtonElement>) => {
    e.preventDefault()

    if(window.confirm('게시글 작성을 취소하시겠습니까?')) {
      navigate('/board')
    } else {
      return false
    }
  }

  return (
    <div className="board-create">
      <Title children="Write content"/>

      <form>
        <div>
          <Label htmlFor="title">Title</Label>
          <Input type="text" id="title" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="제목을 입력해주세요."/>
        </div>

        <div>
          <Label htmlFor="content">Content</Label>
          <Textarea name="content" id="content" value={content} onChange={(e) => setContent(e.target.value)} placeholder="내용을 입력해주세요."/>
        </div>
      </form>

      <div className="grid-2">
        <Button children="Confirm" variant="primary" onClick={formSubmit}/>
        <Button children="Cancel" variant="secondary" onClick={formCancel}/>
      </div>
    </div>
  )
}

export default BoardCreate

 

#4
axios.post()로 title과 content 입력값을 서버에 전송하고,
alert 창으로 밸리데이션 역할을 대신했다.

목표

  • 상세 페이지 만들기
  • axios로 상세 페이지 서버 데이터 받기

 


결과 화면

 


1. App.tsx path 설정하기

  • src/App.tsx

상세페이지의 url이 '/board/숫자' 형식이 될 수 있도록 라우터의 path를 :id로 설정한다.

import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import './assets/scss/common.scss'

import BoardList from './pages/board/list'
import BoardDetail from './pages/board/detail'
import BoardCreate from './pages/board/create'
import BoardModify from './pages/board/modify'

const App = () => {
  return (
    <>
      {
        <BrowserRouter>
          <Routes>
            <Route>
              <Route path="/board" element={<BoardList/>}/>
              <Route path="/board/:id" element={<BoardDetail/>}/>
              <Route path="/board/create" element={<BoardCreate/>}/>
              <Route path="/board/modify/:id" element={<BoardModify/>}/>
            </Route>
          </Routes>
        </BrowserRouter>
      }
    </>
  )
}

export default App

 

 

2. 목록 페이지에서 상세 페이지로 이동하기

  • src/pages/board/list/index.tsx

Link 컴포넌트의 경로를 데이터베이스의 id 값으로 설정한다.

import { Link } from 'react-router-dom'

const BoardList = () => {
  return (
    <div className="board-list">
      <table>
       ...
        <tbody>
          {
            currentPost.map((board, index: number) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title">
                    // 상세페이지로 이동
                    <Link to={`/board/${board.id}`}>{board.title}</Link>
                  </td>
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

export default BoardList

 

3. axios.get()으로 서버 데이터 받기

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

Postman을 통해서 GET으로 받아온 데이터를 확인할 수 있다.

 

 

  • src/pages/board/detail/index.tsx
  1. useEffect()를 안에 axios를 작성한다.
  2. 상세 페이지는 서버에서 데이터를 받아 화면에 보여주면 되기 때문에 get() 메서드를 사용한다.
  3. useParams() 훅을 사용해서 url의 파라미터 값에 해당하는 서버 데이터를 가지고 온다. 
  4. [setDetailBoardData] state 변수에 axios로 받아온 데이터를 저장한다.
import { useParams } from 'react-router'

const BoardDetail = () => {
  // hook
  const params = useParams().id

  // state
  const [detailBoardData, setDetailBoardData] = useState<any>([])

  useEffect(() => {
    axios.get(`http://localhost:3001/board/${params}`)
      .then((response) => {
        setDetailBoardData(response.data)
      })

      .catch(function(error) {
        console.log( error)
      })
  }, [])

  return (
    <div className="board-detail">
	  ...
    </div>
  )
}

export default BoardDetail

 

4. 서버 데이터를 화면에 출력하기

[setDetailBoardData] state 변수에 저장한 데이터를 화면에 출력한다.

const BoardDetail = () => {
  // state
  const [detailBoardData, setDetailBoardData] = useState<any>([])

  useEffect(() => {
    ...
  }, [])

  return (
    <div className="board-detail">
      <Title children={detailBoardData.title}/>

      <div className="board-wrapper">
        <div className="board-header">
          <p>No.{detailBoardData.id}</p>
          <p>{dayjs(detailBoardData.created_at).format('YYYY.MM.DD')}</p>
        </div>

        <div className="board-content">
          <p>{detailBoardData.content}</p>
        </div>
      </div>

      <div className="grid-2">
        <Link to={`/board/modify/${detailBoardData.id}`}>
          <Button children="Modify" variant="primary"/>
        </Link>
        <Link to="/board">
          <Button children="List" variant="secondary"/>
        </Link>
      </div>
    </div>
  )
}

export default BoardDetail

 


전체 코드

  • src/pages/board/detail/index.tsx

FIXME:: state에 데이터를 저장할 때 타입을 any로 설정했다. 수정할 예정이기 때문에 타입에 주의한다.

import { useState, useEffect } from 'react'
import { useParams } from 'react-router'
import { Link, useNavigate } from 'react-router-dom'
import axios from 'axios'
import dayjs from 'dayjs'
import Button from '../../../components/form/Button'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardDetail = () => {
  // hook
  const params = useParams().id
  const navigate = useNavigate()

  // state
  const [detailBoardData, setDetailBoardData] = useState<any>([])

  useEffect(() => {
    axios.get(`http://localhost:3001/board/${params}`)
      .then((response) => {
        setDetailBoardData(response.data)
      })

      .catch(function(error) {
        console.log( error)
      })
  }, [])

  return (
    <div className="board-detail">
      <Title children={detailBoardData.title}/>

      <div className="board-wrapper">
        <div className="board-header">
          <p>No.{detailBoardData.id}</p>
          <p>{dayjs(detailBoardData.created_at).format('YYYY.MM.DD')}</p>
        </div>

        <div className="board-content">
          <p>{detailBoardData.content}</p>
        </div>
      </div>

      <div className="grid-2">
        <Link to={`/board/modify/${detailBoardData.id}`}>
          <Button children="Modify" variant="primary"/>
        </Link>
        <Link to="/board">
          <Button children="List" variant="secondary"/>
        </Link>
      </div>
    </div>
  )
}

export default BoardDetail

 

#3
axios.get()과 useParams()을 통해
id별로 각각의 상세 페이지를 보여줄 수 있도록 만들었다.

목표

  • react-js-pagination 패키지를 사용해서 페이지네이션 컴포넌트 만들기
  • 페이지네이션 기능 구현

 


결과 화면

 


1. react-js-pagination 패키지 설치하기

https://www.npmjs.com/package/react-js-pagination

react-js-pagination 패키지에 대한 자세한 설명은 npm 사이트를 참고한다.

 

타입스크립트로 설치된 리액트이기 때문에 타입스크립트 버전도 같이 설치한다.

yarn add react-js-pagination @types/react-js-pagination
또는
npm install react-js-pagination @types/react-js-pagination

 

2. Pagination 컴포넌트 css 또는 scss 작성하기

react-js-pagination 패키지의 경우, css가 없기 때문에 직접 css 또는 scss를 작성한다.

 

  • src/assets/scss/components/pagination.scss

css/scss를 작성하고, 공통 css/scss에 import 하거나 페이지네이션을 사용한 페이지에 import 한다.

해당 scss는 예시일 뿐, 원하는 디자인으로 만든다.

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 5px;
  margin: 50px auto;

  li {
    width: 30px;
    height: 30px;
    border-radius: 50%;
    background-color: $bg-light;
    transition: all 0.3s;

    &:hover,
    &:active ,
    &.active{
      background-color: $bg-point;
      color: $white;
    }

    &:nth-child(1),
    &:nth-child(2),
    &:nth-last-child(1),
    &:nth-last-child(2) {
      a {
        align-items: baseline;
        font-size: 20px;
      }
    }

    a {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 100%;
      height: 100%;
      font-size: 12px;
    }
  }
}

 

3. react-js-pagination 패키지의 Pagination 컴포넌트 사용하기

  • src/pages/board/list/index.tsx

axios로 받은 전체 게시글 데이터를 slice() 해서 페이지네이션 별로 화면에 보여준다.

 

  1. Pagination 컴포넌트를 import 한다.
  2. [currentPost, setCurrentPost], [page, setPage] state 변수를 만든다.
  3. postPerPage, indexOfLastPost, indexOfFirstPost 변수를 만든다.
  4. handlePageChange() 클릭 이벤트를 만든다. 페이지네이션을 누를 때마다 몇 번째 페이지인지 알 수 있다.
  5. 최근 게시물부터 역순으로 출력하고 싶기 때문에 axios 데이터를 reverse() 한다.
  6. 전체 게시글 또는 페이지네이션이 바뀔 때마다 동작해야 하기 때문에 useEffect() 안에 [setCurrentPost] state 변수를 작성한다.
  7. axios 데이터를 첫 번째 게시글 인덱스 번호와 마지막 게시글 인덱스 번호로 slice() 해서 5개씩 노출시킨다.
import Pagination from 'react-js-pagination'

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([]) // axios에서 받아온 전체 게시글 데이터
  const [currentPost, setCurrentPost] = useState<BoardType[]>(boardList) // 페이지네이션을 통해 보여줄 게시글
  const [page, setPage] = useState<number>(1) // 현재 페이지 번호

  const postPerPage: number = 5 // 페이지 당 게시글 개수
  const indexOfLastPost: number = page * postPerPage
  const indexOfFirstPost: number = indexOfLastPost - postPerPage

  const handlePageChange = (page: number) => {
    setPage(page)
  }

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList([...response.data].reverse())
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  useEffect(() => {
    setCurrentPost(boardList.slice(indexOfFirstPost, indexOfLastPost))
  }, [boardList, page])

  return (
    <div className="board-list">
      <table>
        ...
      </table>

      <Pagination
        activePage={page}
        itemsCountPerPage={postPerPage}
        totalItemsCount={boardList.length}
        pageRangeDisplayed={5}
        prevPageText={"‹"}
        nextPageText={"›"}
        onChange={handlePageChange}/>
    </div>
  )
}

export default BoardList

 


전체 코드

  • src/pages/board/list/index.tsx
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import dayjs from 'dayjs'
import Pagination from 'react-js-pagination'
import { BoardType } from '../../../interface/BoardType'
import Button from '../../../components/form/Button'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardList = () => {
  // state
  const [boardList, setBoardList] = useState<BoardType[]>([]) // axios에서 받아온 게시글 데이터
  const [currentPost, setCurrentPost] = useState<BoardType[]>(boardList) // 게시판 목록에 보여줄 게시글
  const [page, setPage] = useState<number>(1) // 현재 페이지 번호

  const postPerPage = 5 // 페이지 당 게시글 개수
  const indexOfLastPost = page * postPerPage
  const indexOfFirstPost = indexOfLastPost - postPerPage

  const boardLength = boardList.length

  const handlePageChange = (page: number) => {
    setPage(page)
  }

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList([...response.data].reverse())
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  useEffect(() => {
    setCurrentPost(boardList.slice(indexOfFirstPost, indexOfLastPost))
  }, [boardList, page])

  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : {boardLength}</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          {
            currentPost.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title"><Link to={`/board/${board.id}`}>{board.title}</Link></td>
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>

      <Pagination
        activePage={page}
        itemsCountPerPage={postPerPage}
        totalItemsCount={boardList.length}
        pageRangeDisplayed={5}
        prevPageText={"‹"}
        nextPageText={"›"}
        onChange={handlePageChange}/>

      <Link to="/board/create">
        <Button children="Write" variant="primary"/>
      </Link>
    </div>
  )
}

export default BoardList

 

#2.2
react-js-paginatnio 패키지를 설치하고,
scss를 작성해 디자인을 커스텀하고,
axios 데이터를 slice 해서
페이지네이션 기능이 동작하게 만들었다.

목표

  • 목록 페이지 만들기
  • axios.get()으로 목록 페이지 서버 데이터 받기
  • dayjs 패키지를 이용해 날짜 포맷하기

 


결과 화면

 


1. axios 패키기 설치하기

axios 패키기를 설치한다.

이미 설치되어 있다면 설치하지 않아도 된다.

yarn add axios
또는
npm install axios

 

2. axios.get()으로 서버 데이터 받기

https://jae-study.tistory.com/80

Nest.js와 Psql을 사용해서 백앤드와 데이터베이스를 만들었다.

 

직접 백앤드와 데이터베이스를 만들어도 되고,

JSONPlaceholder 사이트를 통해 가짜 데이터를 받아와도 된다.

 

Postman을 통해서 GET으로 받아온 데이터를 확인할 수 있다.

 

 

  • src/pages/board/list/index.tsx
  1. useEffect()를 안에 axios를 작성한다.
  2. 목록 페이지는 서버에서 데이터를 받아 화면에 보여주면 되기 때문에 get() 메서드를 사용한다.
  3. [setBoardList] state 변수에 axios로 받아온 데이터를 저장한다.
const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList(response.data)
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  return (
    <div className="board-list">
      <table>
	...
      </table>
    </div>
  )
}

export default BoardList

 

3. 서버 데이터를 화면에 출력하기

  • src/pages/board/list/index.tsx

[setBoardList] state 변수에 저장한 데이터를 map() 메서드를 사용해 화면에 출력한다.

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  useEffect(() => {
    ...
  }, [])

  return (
    <div className="board-list">
        ...
        <tbody>
          {
            boardList.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title">
                    <Link to={`/board/${board.id}`}>{board.title}</Link>
                  </td>
                  <td>{board.created_at}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

 

4. dayjs 패키지를 사용해 날짜 포맷하기

dayjs 패키지를 설치한다.

yarn add dayjs
또는 
npm install dayjs

 

  • src/pages/board/list/index.tsx

dayjs().format()에 원하는 날짜 형식을 작성한다.

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  useEffect(() => {
    ...
  }, [])

  return (
    <div className="board-list">
        ...
        <tbody>
          {
            boardList.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title">
                    <Link to={`/board/${board.id}`}>{board.title}</Link>
                  </td>
                  
                  // 수정한 부분
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

 


전체 코드

  • src/pages/board/list/index.tsx
import { useState, useEffect } from 'react'
import { Link } from 'react-router-dom'
import axios from 'axios'
import dayjs from 'dayjs'
import { BoardType } from '../../../interface/BoardType'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardList = () => {
  const [boardList, setBoardList] = useState<BoardType[]>([])

  const boardLength = boardList.length

  useEffect(() => {
    axios.get('http://localhost:3001/board')
      .then((response) => {
        setBoardList(response.data)
      })

      .catch(function(error) {
        console.log(error)
      })
  }, [])

  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : {boardLength}</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          {
            boardList.map((board, index) => {
              return (
                <tr key={index}>
                  <td>{index + 1}</td>
                  <td className="title"><Link to={`/board/${board.id}`}>{board.title}</Link></td>
                  <td>{dayjs(board.created_at).format('YYYY.MM.DD')}</td>
                </tr>
              )
            })
          }
        </tbody>
      </table>
    </div>
  )
}

export default BoardList

 

#2.1
axios.get()을 통해서 서버 데이터를 받아오고,
map() 메서드를 통해 화면에 출력한 뒤,
dayjs 패키지를 사용해서 날짜를 포맷했다.

목표

  • 게시판 목록 페이지 만들기
  • 게시판 상세 페이지 만들기
  • 게시판 작성 페이지 만들기
  • 게시판 수정 및 삭제 페이지 만들기

Typescript로 React를 설치하고, 게시판에 필요한 페이지들을 만든다.

 


결과 화면

 


1. 타입스크립트로 리액트 프로젝트 설치하기

https://jae-study.tistory.com/70

리액트 설치에 대한 자세한 설명은 위의 링크를 참고한다.

 

터미널에서 아래 명령어를 실행해 타입스크립트로 리액트 프로젝트를 설치한다.

yarn create react-app 폴더명 --template typescript
또는
npx create-react-app 폴더명 --template typescript

 

 

2. 게시판 폴더 구조

  • 목록 : src/pages/board/list/index.tsx
  • 상세 : src/pages/board/detail/index.tsx
  • 생성 : src/pages/board/create/index.tsx
  • 수정 : src/pages/board/modify/index.tsx

게시판 목록, 상세, 생성, 수정, 총 4가지 페이지를 만든다.

 

 

3. 마크업, css/scss 적용하기

https://jae-study.tistory.com/72

리액트에 scss를 적용하고 싶으면 위의 링크를 참고한다.

 

페이지 별로 마크업을 하고,

css 또는 scss 파일을 작성한 뒤, 각 index.tsx 파일에 import 해준다.

 

  • src/pages/board/list/index.tsx

해당 마크업은 예시일 뿐, 원하는 디자인으로 목록, 상세, 생성, 수정 페이지를 만든다.

import { Link } from 'react-router-dom'
import Button from '../../../components/form/Button'
import Title from '../../../components/text/Title'
import './index.scss'

const BoardList = () => {
  return (
    <div className="board-list">
      <Title children="Board list"/>

      <h4>Total post : 10개</h4>

      <table>
        <colgroup>
          <col width="15%"/>
          <col width="65%"/>
          <col width="20%"/>
        </colgroup>

        <thead>
          <tr>
            <th>No</th>
            <th>Title</th>
            <th>Date</th>
          </tr>
        </thead>

        <tbody>
          <tr>
            <td>1</td>
            <td>
              <Link to={`/board/1`}>공지사항 입니다.</Link>
            </td>
            <td>2023.06.30</td>
          </tr>
        </tbody>
      </table>

      <Link to="/board/create">
        <Button children="Write" variant="primary"/>
      </Link>
    </div>
  )
}

export default BoardList

 

  • src/pages/board/list/index.scss
@import '../../../assets/scss/base/variable';

.board-list {
  h4 {
    margin-bottom: 20px;
    font-weight: 700;
  }

  table {
    background-color: $bg-light;
    border-radius: 5px;

    thead {
      th {
        padding: 20px 0;
        font-size: 20px;
        font-weight: 700;
      }
    }

    tbody {
      border-top: 1px solid $bg-point;

      td {
        padding: 25px 0;
        text-align: center;
        vertical-align: middle;

        &.title {
          text-align: left;

          a {
            padding-bottom: 2px;

            &:hover {
              border-bottom: 1px solid $bg-point;
            }
          }
        }
      }
    }
  }
}

 

4. 라우터 연결하기

https://jae-study.tistory.com/73

react-router-dom에 대한 자세한 설명은 위의 링크를 참고한다.

 

react-router-dom 패키지를 설치한다.

yarn add react-router-dom
또는
npm install react-router-dom

 

  • src/index.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App'

const root = ReactDOM.createRoot (
  document.getElementById('root') as HTMLElement
)

root.render(
  <React.StrictMode>
    <App/>
  </React.StrictMode>
)

 

  • src/App.tsx

BrowserRouter, Routes, Route 패키지를 import 하고,

<Route> 컴포넌트의 path를 설정한다.

게시판 상세와 수정 페이지는 게시글 별로 페이지가 있기 때문에 :id 값을 받아야 한다.

import React from 'react'
import { BrowserRouter, Routes, Route } from 'react-router-dom'
import './assets/scss/common.scss'

import BoardList from './pages/board/list'
import BoardDetail from './pages/board/detail'
import BoardCreate from './pages/board/create'
import BoardModify from './pages/board/modify'

const App = () => {
  return (
    <>
      {
        <BrowserRouter>
          <Routes>
            <Route path="/board" element={<BoardList/>}/>
            <Route path="/board/:id" element={<BoardDetail/>}/>
            <Route path="/board/create" element={<BoardCreate/>}/>
            <Route path="/board/modify/:id" element={<BoardModify/>}/>
          </Routes>
        </BrowserRouter>
      }
    </>
  )
}

export default App

 

#1
타입스크립트로 리액트를 설치하고,
게시판 목록, 상세, 생성, 수정 페이지를 만들고,
라우터 연결까지 완료했다.

+ Recent posts