일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- Redux
- userevent_tracker
- Excel
- methodChannel
- webrtc
- Prism.js
- Babel standalone
- Three js
- KakaoMap
- web track
- FirebaseAnalytics
- react
- uint16array
- Flutter
- babel
- Raycasting
- uint8array
- code editor
- RouteObserver
- Image Resize typescript
- typescript
- androidId
- Three-fiber
- jszip
- swagger-typescript-api
- identifierForVender
- REST API
- Completer
- Game js
- node
- Today
- Total
Never give up
React, Node - Rest API with sqlite3 (beginner) - 2 본문
이전 포스트에 이어서 react부분을 정리해봤습니다
(링크 : https://devmemory.tistory.com/78)
다만 class형만 다루고 함수형은 나중에 숙련도가 조금 더 올라가면
함수형으로 간단한 예제를 한번 만들어볼까 합니다
api
import { response } from 'util/util'
const baseURL = "api"
// 성공: code: 1, data : object
// 실패: code: -1, data : 실패 message
class API {
getTasks = async (pageNo, pageSize) => {
let url = `${baseURL}/task`
if(pageNo){
url += `?pageNo=${pageNo}`
}
if(pageSize){
url += `&pageSize=${pageSize}`
}
console.log(url)
const res = await response(url)
return {
data: res.data,
code: res.code
}
}
getSingleTask = async (id) => {
const res = await response(`${baseURL}/task/${id}`)
return {
data: res.data,
code: res.code
}
}
// task
addTask = async (task) => {
const res = await response(`${baseURL}/task/add`, 'POST', task)
return {
data: res.data,
code: res.code
}
}
deleteTask = async (id) => {
const res = await response(`${baseURL}/task/delete`, 'POST', { id })
return {
data: res.data,
code: res.code
}
}
toggleReminder = async (id) => {
const res = await response(`${baseURL}/task/toggle`, 'POST', { id })
return {
data: res.data,
code: res.code
}
}
}
export { API }
util
import axios from "axios"
// fetch
const tryFetch = async (url,option) => {
let data
let code
try {
const res = await fetch(url,option)
const jsonData = await res.json()
data = jsonData.data
code = jsonData.code
if(res.status !== 200){
throw new Error(`Failed to fetch. status : ${res.status}`)
}
} catch(e){
data = e
code = -1
}
return {
data,
code
}
}
// axios
const response = async (url,method,data) => {
let result
let code
try {
const res = await axios({url, method, data})
result = res.data.data
code = res.data.code
if(res.status !== 200){
throw new Error(`Failed to fetch. status : ${res.status}`)
}
} catch(e){
result = e
code = -1
}
return {
data: result,
code
}
}
export { tryFetch, response }
axios, fetch테스트용도로 일단 만들어놨는데 전반적으로 axios가 조금 더 간단한거 같은데
패키지 설치 및 업데이트를 생각하면 fetch를 사용하는것도 괜찮을것 같다는 생각이 들었습니다
그리고 js특성상 status code가 다른게 넘어와도
error로 인식을 안되어 별도로 처리해줘야되는 부분이 조금 아쉬웠던것 같습니다
app
import { Component } from "react"
import Header from 'component/todo/class/header'
import Tasks from 'component/todo/class/tasks'
import AddTask from 'component/todo/class/add_task'
import { API } from 'api/common'
import { Center } from "style/styled"
import 'style/task_style.css'
import { Spinner } from "react-bootstrap"
import PaginationButton from "component/todo/class/pagination_button"
class ClassApp extends Component {
constructor() {
super()
this.state = {
showAddTask: false,
tasks: [],
isLoaded: false
}
this.page = {}
this.pageSize = 3
this.api = new API()
this.addTask = this.addTask.bind(this)
this.deleteTask = this.deleteTask.bind(this)
this.toggleReminder = this.toggleReminder.bind(this)
this.pagination = this.pagination.bind(this)
}
async initTask() {
// this.api.getTasksAjax()
const result = await this.api.getTasks(1, this.pageSize)
if (result.code === 1) {
this.page = {
totalCount: result.data.totalCount,
currentPage: result.data.currentPage,
lastPage: Math.ceil(result.data.totalCount / this.pageSize)
}
this.setState({ tasks: result.data.list, isLoaded: true })
} else {
alert(result.data)
}
}
async pagination(index) {
if (index <= this.page.lastPage) {
const result = await this.api.getTasks(index, this.pageSize)
if (result.code === 1) {
this.page.currentPage = result.data.currentPage
this.setState({ tasks: result.data.list })
} else {
alert(result.data)
}
} else {
alert('Last Page!')
}
}
componentDidMount() {
this.initTask()
}
async addTask(task) {
const result = await this.api.addTask(task)
if (result.code === 1) {
if (this.state.tasks) {
this.setState({ tasks: [result.data, ...this.state.tasks] })
} else {
this.setState({ tasks: [result.data] })
}
} else {
alert(result.data)
}
}
async deleteTask(id) {
const result = await this.api.deleteTask(id)
if (result.code === 1) {
const task = this.state.tasks.find((element) => element.id === result.data)
task.hide = true
this.setState({ tasks: this.state.tasks.filter((element) => !element.hide) })
} else {
alert(result.data)
}
}
async toggleReminder(id) {
const result = await this.api.toggleReminder(id)
if (result.code === 1) {
const task = this.state.tasks.find((element) => element.id === result.data)
task.reminder = !task.reminder
this.setState({ tasks: this.state.tasks })
} else {
alert(result.data)
}
}
render() {
if (!this.state.isLoaded) {
return (
<Center height='100vh'>
<Spinner animation='grow' />
</Center>
)
}
return (
<div className="div-container">
<Header
onAdd={() => this.setState({ showAddTask: !this.state.showAddTask })}
showAdd={this.state.showAddTask} />
{this.state.showAddTask && <AddTask onAdd={this.addTask} />}
{(this.state.tasks?.length ?? -1) > 0 ?
<Tasks tasks={this.state.tasks} onDelete={this.deleteTask} onToggle={this.toggleReminder} /> : 'No tasks to show'}
<PaginationButton page={this.page} pagination={this.pagination} />
</div>
)
}
}
export default ClassApp;
이전과 다르게 init부분에 처리가 조금 더 많아졌는데,
pagination을 구현하기 위해서 pageModel을 추가했습니다
제가 사용할 데이터는 전체 개수, 현재 페이지, 그리고 마지막 페이지(계산식)
마지막 페이지는 (전체 개수 / pageSize)를 해서 ceil값을 가져옵니다
예를들어 45 / 10 이면 4.5인데 여기서 4로 처리를 해버리면
4 페이지(40개)만 보여주고 나머지 5개를 못보여주기 때문에 ceil을 사용했습니다
그리고 pagination 함수에서 page button을 누를때마다
페이지에 따른 api 콜 및 화면 갱신(setState)를 해주고 있습니다
styled
import styled from "styled-components";
export const PageButton = styled.span`
margin: 4px;
padding: 6px;
background: ${(props) => props.background};
color: white;
cursor: pointer;
text-align: center;
font-size: 14px;
&:hover {
background: #b4b4b4
}
`;
pagination button
import { Component } from 'react'
import { PageButton } from 'style/styled'
import { AiFillCaretLeft, AiFillCaretRight } from "react-icons/ai"
class PaginationButton extends Component {
constructor(props) {
super(props)
this.pageRange = 1
this.lastPageRange = Math.ceil(props.page.lastPage / 10)
this.pageList = this.pageList.bind(this)
this.changeRange = this.changeRange.bind(this)
}
pageList() {
const start = (this.pageRange - 1) * 10 + 1
const end = 10 * this.pageRange > this.props.page.lastPage ? this.props.page.lastPage : 10 * this.pageRange
return Array(end - start + 1).fill().map((_, index) => start + index)
}
changeRange(isAdd) {
this.pageRange = isAdd ? this.pageRange + 1 : this.pageRange - 1
this.props.pagination(isAdd ? (this.pageRange - 1) * 10 + 1 : this.pageRange * 10)
}
render() {
const { page, pagination } = this.props
return (
<>
{this.pageRange > 1 ? <PageButton key='previous' background='grey' onClick={() => this.changeRange(false)}>
<AiFillCaretLeft />
</PageButton> : <></>}
{this.pageList().map((e) => (
<PageButton key={e} background={page.currentPage === e ? '#bebebe' : 'grey'} onClick={() => pagination(e)}>
{e}
</PageButton>
))}
{this.lastPageRange > this.pageRange ? <PageButton key='next' background='grey' onClick={() => this.changeRange(true)}>
<AiFillCaretRight />
</PageButton> : <></>}
</>
)
}
}
export default PaginationButton
이부분은 필자가 고민을 조금한 포인트가 있었는데
페이지 리스트 생성부분 그리고 상태 변경부분이었는데
리스트 생성부분은
1~10, 11~20, 21~30 => (n-1) * 10 + 1 ~ 10 * n으로 start, end포인트를 만들고
Array(end - start + 1).fill()을 해주면 개수가 10인 빈값 배열이 생성되고
map을 이용해서 start + index를 해줘서 1~10, 11~20등을 만들어줍니다
페이지 범위 변경부분은 다행이도(?) app부분에서 setState가 일어나서
해당 컴포넌트에서는 추가로 처리를 안해줘도 잘 작동이 되었습니다
task
import { Component } from 'react'
import { FaTimes } from 'react-icons/fa'
class Task extends Component {
render() {
const { task, onDelete, onToggle } = this.props
return (
<div className={`task ${task.reminder ? 'reminder' : ''}`} onDoubleClick={() => onToggle(task.id)}>
<h5>
{task.title} <FaTimes style={{ color: 'red', cursor: 'pointer' }} onClick={() => onDelete(task.id)} />
</h5>
<p>{task.day}</p>
</div>
)
}
}
export default Task
tasks
import { Component } from 'react'
import Task from './task'
class Tasks extends Component {
render() {
const { tasks, onDelete, onToggle } = this.props
return (
<div>
{tasks.map((task) => (
<Task key={task.id} task={task} onDelete={onDelete} onToggle={onToggle} />
))}
</div>
)
}
}
export default Tasks
header
import Button from './button'
import { Component } from 'react'
class Header extends Component {
render() {
const {title, showAdd, onAdd} = this.props
return (
<header className="header-task">
<h1>{title}</h1>
<Button color={showAdd ? 'red' : 'green'} text={showAdd ? 'Close' : 'Add'} onClick={onAdd} />
</header>
)
}
}
export default Header
button
import { Component } from "react"
class Button extends Component {
render() {
const { color, text, onClick } = this.props
return (
<button onClick={onClick} style={{ backgroundColor: color }} className="btn-task">{text}</button>
)
}
}
export default Button
add task
import { Component } from "react"
class AddTask extends Component {
constructor(props) {
super(props)
this.state = {
title: "",
day: "",
reminder: false
}
this.onSubmit = this.onSubmit.bind(this)
}
onSubmit(e) {
e.preventDefault()
if (this.state.title === "") {
alert('Please add a task')
return
}
this.props.onAdd({ title: this.state.title, day: this.state.day, reminder: this.state.reminder })
this.setState({
title: "",
day: "",
reminder: false
})
}
render() {
return (
<form className="form-add-task" onSubmit={(e) => this.onSubmit(e)}>
<div className="form-control-task">
<label>Task</label>
<input type="text" placeholder="Add Task" value={this.state.title} onChange={(e) => this.setState({title: e.target.value})} />
</div>
<div className="form-control-task">
<label>Day & Time</label>
<input type="text" placeholder="Add Day & Time" value={this.state.day} onChange={(e) => this.setState({day: e.target.value})} />
</div>
<div className="form-control-task form-control-check">
<label>Set Reminder</label>
<input type="checkbox" value={this.state.reminder} checked={this.state.reminder} onChange={(e) => this.setState({reminder: e.currentTarget.checked})} />
</div>
<input type="submit" value="Save Task" className="btn-task-block" />
</form>
)
}
}
export default AddTask
task style css
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400&display=swap');
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: 'Poppins', sans-serif;
}
.div-container {
max-width: 500px;
margin: 30px auto;
overflow: auto;
min-height: 300px;
border: 1px solid steelblue;
padding: 30px;
border-radius: 5px;
}
.header-task {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.btn-task {
display: inline-block;
background: #000;
color: #fff;
border: none;
padding: 10px 20px;
margin: 5px;
border-radius: 5px;
cursor: pointer;
text-decoration: none;
font-size: 15px;
font-family: inherit;
}
.btn-task:focus {
outline: none;
}
.btn-task:active {
transform: scale(0.98);
}
.btn-task-block {
display: block;
width: 100%;
}
.task {
background: #f4f4f4;
margin: 5px;
padding: 10px 20px;
cursor: pointer;
}
.task.reminder {
border-left: 5px solid green;
}
.task h5 {
display: flex;
align-items: center;
justify-content: space-between;
}
.form-add-task {
margin-bottom: 40px;
}
.form-control-task {
margin: 20px 0;
}
.form-control-task label {
display: block;
}
.form-control-task input {
width: 100%;
height: 40px;
margin: 5px;
padding: 3px 7px;
font-size: 17px;
}
.form-control-check {
display: flex;
align-items: center;
justify-content: space-between;
}
.form-control-check label {
flex: 1;
}
.form-control-check input {
flex: 2;
height: 20px;
}
footer {
margin-top: 30px;
text-align: center;
}
컴포넌트 부분은 다른분이 함수형으로 만들어놓은거 class형으로 만들어놓은거라
딱히 중요하거나 어려웠던 포인트는 없었던것 같으니 자세한 설명은 생략합니다
마지막으로 구현된 화면 영상입니다
'WEB' 카테고리의 다른 글
React - Kakaomap example (0) | 2022.02.19 |
---|---|
React - html, css album page clone (0) | 2022.01.08 |
React, Node - Rest API with sqlite3 (beginner) - 1 (0) | 2022.01.08 |
개발환경 셋팅 - Vs code extension(Web), 단축키 변경 (0) | 2021.11.20 |
개발환경 셋팅 - Windows에서 Node .zip 원하는 경로에 설치 (0) | 2021.11.20 |