카카오 도서 검색 API를 활용한 책 정보 검색기능을 구현해보았다.
키워드를 입력하면 해당되는 조회결과를 출력해준다. 하단에 paging 처리도 추가로 진행하였다.
번호를 클릭하면 하단에 선택한 도서의 상세 정보를 조회할 수 있다.
[App.js] : 최상단 컴포넌트
컴포넌트를 구현하고 라우팅 경로를 설정하였다.
import React from 'react';
import {BrowserRouter, Link, Route, Routes} from 'react-router-dom'
import Signup from './component/signup';
import Book from './component/book';
import History from './component/history';
import Login from './component/login'
import Keyword from './component/keyword';
import BookDetail from './component/bookDetail';
import './App.css'
function App() {
return (
<div className="App">
<BrowserRouter>
<header>
<Link to ="/book">
<button>도서 검색</button>
</Link>
<Link to ="/history">
<button>내 검색 히스토리</button>
</Link>
<Link to ="/keyword">
<button>인기 키워드 목록</button>
</Link>
</header>
<Routes>
<Route path="/" element = {<Login/>}/>
<Route path="/signup" element = {<Signup/>}/>
<Route path="/book" element = {<Book/>}/>
<Route path="/history" element = {<History/>}/>
<Route path="/keyword" element = {<Keyword/>}/>
<Route path="/book/detail/:title" element = {<BookDetail/>}/>
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
[book.js] Book 컴포넌트 : 도서정보
import { useState } from "react";
import { Link } from "react-router-dom";
export default function Book(){
if(!localStorage.getItem("userId"))
{
window.location.href = "/" //세션 스트리지에 userId값이 없으면 로그인 페이지로 리디렉트
}
const [keyword, setKeyword] = useState("");
const [bookList, setBookList] = useState([]);
console.log("bookList : " + bookList);
const getBookList = () => {
fetch(`http://localhost:8080/kakao/${keyword}`) //입력한 키워드로 API 요청
.then((response) => response.json())
.then((data) => {setBookList(data.documents)
console.log(data.documents)}
);
}
const keywordChange = (e) => {
setKeyword(e.target.value)
console.log(e.target.value)
}
return(
<div>
<h1>도서 검색 페이지 입니다</h1>
<div>
<p>키워드 입력 <input className = "keywordBook" type="text" onChange={keywordChange} placeholder="검색할 도서명을 입력하세요"/></p>
<p><input type="button" onClick={getBookList} value="검색" className ="submit-btn"/></p>
</div>
<table style={{margin:"0px auto"}}>
<tbody>
<tr>
<th>도서명</th>
<th>저자</th>
<th>가격</th>
<th>출판사</th>
<th>isbn</th>
</tr>
{bookList.map((book) => (
<tr key={book.title}>
<td><Link to = {`/book/detail/${book.title}`}>{book.title}</Link></td>
{/*이 컬럼을 클릭하면 bookDetail 컴포넌트로 현재의 상태값(bookList : API 조회 결과값)을 전달하는 방법을 생각중*/}
<td>{book.authors}</td>
<td>{book.price}</td>
<td>{book.publisher}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
</div>
)
}
[bookDetail.js] 도서 검색 상세페이지
위의 Book에서 도서 제목 ({book.title})을 클릭하면 상세 페이지로 이동하여 상세한 상품정보를 표시하는 컴포넌트이다.
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom"
export default function BookDetail(props){
let {title} = useParams();
let findBook = props.bookList.find((book) => {
return book.title === title;
}); //prop으로 Book 컴포넌트(book.js) 파일에서 전달 받고 해당되는 도서의 정보(유일한 값) 검색
/*const [findBook, bookInfo] = useState([]);
useEffect(()=>{
fetch(`http://localhost:8080/kakao/${title}`)
.then((response) => response.json())
.then((data) => {bookInfo(data.documents)
console.log(data.documents)}
);
},[])
*/
return(
<table>
<tbody>
<tr>
<th>제목</th>
<th>도서 썸네일</th>
<th>소개</th>
<th>ISBN</th>
<th>저자</th>
<th>출판사</th>
<th>출판일</th>
<th>정가</th>
<th>판매가</th>
</tr>
<tr>
<td>{findBook.title}</td>
<td><img src = "{findBook.thumbnail}" alt="thumbnail"/></td>
<td>{findBook.contents}</td>
<td>{findBook.isbn}</td>
<td>${findBook.author.toString()}</td> // response에 authors은 Array로 되어있는데
이 Array를 문자열로 변환
<td>{findBook.publisher}</td>
<td>{findBook.datetime}</td>
<td>{findBook.price}</td>
<td>{findBook.sale_price}</td>
</tr>
</tbody>
</table>
)
}
현재 위와 같이 상세페이지 컴포넌트(BookDetail)를 구현 중인데, Book 컴포넌트에 있는 상태데이터 (bookList)를 BookDetail 컴포넌트로 전달할 방법을 생각중인데, 해결 방법을 생각해보았다.
해결방안을 찾지 못해 차선으로 Book 컴포넌트 아래에 Hook을 이용하여(isbookDetailShow 변수 설정) bookDetail 표시여부를 조작하기로 결정했다.
[App.js]
import React, { useState } from 'react';
import {BrowserRouter, Link, Route, Routes} from 'react-router-dom'
import Signup from './component/signup';
import Book from './component/book';
import History from './component/history';
import Login from './component/login'
import Keyword from './component/keyword';
import './App.css'
function App() {
const logout = () =>{
localStorage.removeItem("userId")
window.location.href ="/"
}
return (
<div className="App">
<BrowserRouter>
<header>
<Link to ="/book">
<button>도서 검색</button>
</Link>
<Link to ="/history">
<button>내 검색 히스토리</button>
</Link>
<Link to ="/keyword">
<button>인기 키워드 목록</button>
</Link>
{localStorage.getItem("userId") && <button onClick={logout}>로그아웃</button>}
</header>
<Routes>
<Route path="/" element = {<Login/>}/>
<Route path="/signup" element = {<Signup/>}/>
<Route path="/book" element = {<Book/>}/>
<Route path="/history" element = {<History/>}/>
<Route path="/keyword" element = {<Keyword/>}/>
{/*<Route path="/book/detail/:title" element = {<BookDetail/>} 사용하지 않음/>*/}
</Routes>
</BrowserRouter>
</div>
);
}
export default App;
[book.js]
컴포넌트 하단에 isBookDetailShow 여부(true/false)에 따라 bookDetail 컴포넌트의 활성 / 비활성
여부를 설정하였다.
그리고 페이징을 처리하기 위해 react-js-pagination 라이브러리를 활용했다.
import { useEffect, useState } from "react";
import { Link } from "react-router-dom";
import Paging from "./paging";
import BookDetail from "./bookDetail";
export default function Book(){
if(!localStorage.getItem("userId"))
{
window.location.href = "/"
}
const userId = localStorage.getItem("userId")
const [page, setPage] = useState(1)
const [size, setSize] = useState(10)
const [totalCount, setTotalCount] = useState("")
const [keyword, setKeyword] = useState("");
const [bookList, setBookList] = useState([]);
const [isbookDetailShow, setBookDetailShow] = useState(false);
const [bookDetail , setBookDetail] = useState([])
const getBookList = async () => {
const url = `http://localhost:8080/kakao?keyword=${keyword}&page=${page}&size=${size}&userId=${userId}`
await fetch(url)
.then((response) => response.json())
.then((data) => {setBookList(data.documents)
setTotalCount(data.meta.total_count)})
.catch((error)=>{
console.log(error)
})
;
}
useEffect(()=>{
console.log(page)
getBookList()
},[page])
const keywordChange = (e) => {
setKeyword(e.target.value)
console.log(e.target.value)
}
const pageChange = (page) => {
setPage(page)
}
const sizeChange = (e) => {
setSize(e.target.value)
}
const showDetail = (e) =>{
const number = parseInt(e.target.innerText)
setBookDetailYN(true)
setBookDetail(bookList[number-1])
}
const bookDetailClose = () =>{
isBookDetailShow(false)
}
return(
<div>
<h1>도서 검색 페이지 입니다</h1>
<div>
<p>키워드 입력 <input className = "keywordBook" type="text" onChange={keywordChange} placeholder="검색할 도서명을 입력하세요"/></p>
<span>페이지당 표시할 검색 갯수 <input className = "keywordNumber" onChange = {sizeChange} type="text"/></span>
<p><input type="button" onClick={getBookList} value="검색" className ="submit-btn"/></p>
<strong><p style={{color:"red"}}>번호 클릭시 페이지 하단에 해당 도서의 상세정보가 표시됩니다.</p></strong>
</div>
<table style={{margin:"0px auto"}}>
<tbody>
<tr>
<th>번호</th>
<th>도서명</th>
<th>저자</th>
<th>가격</th>
<th>출판사</th>
<th>isbn</th>
</tr>
{bookList.map((book, index) => (
<tr key={index}>
<td onClick={showDetail}>{index+1}</td>
<td>{book.title}</td>
<td>{book.authors}</td>
<td>{book.price}</td>
<td>{book.publisher}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
<Paging totalCount={totalCount} page={page} postPerPage={size} pageRangeDisplayed={10}
handlePageChange={pageChange} />
{isbookDetailShow && <BookDetail bookDetail = {bookDetail} bookDetailClose={bookDetailClose}/>}
</div>
)
}
<Paging.js>
- activePage : 현재 페이지
- itemsCountPerPage : 한 페이지당 보여줄 리스트 아이템의 개수
- totalItemsCount : 총 아이템의 개수
- pageRangeDisplayed : Paginator 내에서 보여줄 페이지의 범위
- pagePageText : "이전"을 나타낼 텍스트(prev, < ,...)
- nextPageText : "다음"을 나타낼 텍스트(next, > , ...)
- onChange : 페이지가 바뀔때 핸들링 해줄 함수
출처: https://cotak.tistory.com/112 [TaxFree:티스토리]
import React from "react";
import Pagination from "react-js-pagination";
import '../../src/paging.css';
const Paging = ({ totalCount, postPerPage, pageRangeDisplayed, handlePageChange, page }) => {
return (
<Pagination
activePage={page}
itemsCountPerPage={postPerPage}
totalItemsCount={totalCount ? totalCount : 0}
pageRangeDisplayed={pageRangeDisplayed}
prevPageText={"‹"}
nextPageText={"›"}
onChange={handlePageChange} />);
};
export default Paging;