Route
1. React Router 사용
2.
npm install react-router-dom @ types/react-router-dom --save
Endpoint
1. 로그인 /login
2. 회원가입 / signup
3. 비밀번호 초기화 /reset
4. 도서 목록 /books
5. 도서 상세 /books/{id}
6. 장바구니 /cart
7. 주문서 작성 /order
8. 주문 목록 /orderlist
Route 지정해주기
react-router-dom의 createBroserRouter, RouterProvider 을 사용해 route를 지정해줄 수 있습니다.
- createBrowserRouter: 라우트 설정을 자바스크립트 객체 배열 형태로 생성, 어떤 경로(path)에서 어떤 컴포넌트(element)를 보여줄지 정의하는 리스트
- RouterProvider: 위에서 생성한 라우터 객체를 실제 React 앱에 주입하는 역할, 앱의 최상단에서 한 번만 사용합니다.
App.tsx
import Layout from "./components/layout/Layout";
import Home from "./pages/Home";
import ThemeSwitcher from "./components/header/ThemeSwitcher";
import { useContext } from "react";
import { BookStoreThemeProvider, ThemeContext } from "./context/themeContext";
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
},
{
path: "./books",
element: <div>도서 목록</div>
}
])
function App() {
const {themeName, toggleTheme} = useContext(ThemeContext);
return (
<>
<BookStoreThemeProvider>
<Layout>
<RouterProvider router={router} />
</Layout>
</BookStoreThemeProvider>
</>
);
}
export default App;
현재에는 예상하지 못한 url 로 들어오면 에러페이지가 뜨는 상태. 이 공통적인 에러 페이지를 만들어보겠습니다.

📄 Error.tsx
import { useRouteError } from "react-router-dom";
interface RouteError {
statusText?: string;
message?: String;
}
function Error() {
const error = useRouteError() as RouteError;
return (
<div>
<h1>오류가 발생했습니다.</h1>
<p>다음과 같은 오류가 발생했습니다. </p>
<p>{error.statusText || error.message}</p>
</div>
)
}
📄 app.ts
path: "/"의 errorElement 추가합니다.
const router = createBrowserRouter([
{
path: "/",
element: <Home />,
errorElement: <Error />
},
{
path: "/books",
element: <div>도서 목록</div>
}
])

📄 Header.tsx
{
CATEGORY.map((item) => (
<li key={item.id}>
<a href={item.id === null ? `/books` : `/books?category_id=${item.id}`}>
{item.name}</a>
</li>
))
}
<a> 태그는 '새로고침'을 발생시키고, <Link>는 '컴포넌트 교체'만 발생시키기 때문에, Link 로 수정해야지 화면깜빡임 없이 화면이동하는 걸로 보여집니다.
| 구분 | <a> 태그 (Standard HTML) | <Link> 컴포넌트 (React Router) |
| 동작 방식 | 브라우저가 서버에 새로운 HTML을 요청함 | 브라우저 주소창의 URL만 변경하고 JS로 화면을 그림 |
| 상태 유지 | 앱 전체가 새로고침되어 모든 상태(State)가 초기화됨 | 기존 상태(State)가 유지됨 (예: 입력 중인 텍스트 등) |
| 속도/사용성 | 화면 깜빡임이 발생하고 속도가 상대적으로 느림 | 필요한 부분만 갈아끼우므로 부드럽고 빠름 (SPA 방식) |
{
CATEGORY.map((item) => (
<li key={item.id}>
<Link to={item.id === null ? `/books` : `/books?category_id=${item.id}`}>
{item.name}</Link>
</li>
))
}
function App() {
const {themeName, toggleTheme} = useContext(ThemeContext);
return (
<>
<BookStoreThemeProvider>
<Layout>
<RouterProvider router={router} />
</Layout>
</BookStoreThemeProvider>
</>
);
}
export default App;
그러나 여기서 Router를 관리하는 주체(RouterProvider)가 Layout의 하위에 있기 때문에 오류가 발생합니다 .
function App() {
const {themeName, toggleTheme} = useContext(ThemeContext);
return (
<>
<BookStoreThemeProvider>
<RouterProvider router={router} />
</BookStoreThemeProvider>
</>
);
}
export default App;
import { useRouteError } from "react-router-dom";
import Layout from "../layout/Layout";
interface RouteError {
statusText?: string;
message?: String;
}
function Error() {
const error = useRouteError() as RouteError;
return (
<Layout>
<h1>오류가 발생했습니다.</h1>
<p>다음과 같은 오류가 발생했습니다. </p>
<p>{error.statusText || error.message}</p>
</Layout>
)
}
export default Error;
이렇게 수정해주어야합니다.
프로젝트 모델 정의
book.model.ts
export interface Book {
id: number;
title: string,
img: 7,
category_id: 0,
form: string;
isbn: string;
summary: string;
detail: string;
author: string;
pages: number;
contents: string;
price: number;
likes: number;
pubDate: string;
}
export interface BookDetail extends Book {
categoryName: string;
liked: boolean;
}
cart.model.ts
export interface Cart {
id: number;
bookId: number;
title: string;
sumamry: string;
quantity: number;
price: number;
}
category.model.ts
export interface Category {
id: number | null;
name: string;
}
order.model.ts
export interface Order {
id: number;
createdAt: string;
address: string;
receiver: string;
contact: string;
bookTitle: string;
totalQuantity: number;
totalPrice: number;
}
pagination.model.ts
export interface Pagination {
currentPage: number;
totalNumber: number;
}
user.model.ts
export interface User {
id: number;
email: string;
password?: string;
}
데이터 흐름
View(Header.tsx) -> Hooks(useCategory) -> Query Library(optional) -> Fetcher (category.api.ts(http.ts)) -> API server
처음으로 백엔드 서버와 연결을 해보겠습니다. 제 인생 처음이에요 ㅎㅎ
Docker, MySQL 포함 백엔드 서버를 실행을 시켜주고 실행을 해주어야하고,
CORS 관련 문제도 해결해야 연결이 됐습니다 .
https://ilove-ya.tistory.com/215
[Axios] Axios Network Error & CORS 오류 해결
문제 상황프론트에서 Axios 요청을 보냈을 때 다음과 같은 에러가 발생했다. AxiosError: Network Error개발자 도구 콘솔에서는 추가로 아래 에러가 확인되었다.Access to XMLHttpRequest at 'http://localhost:9999/cate
ilove-ya.tistory.com
연결 중에 발생했던 오류도 첨부합니다..
📄 http.ts
import axios, {Axios, AxiosRequestConfig} from 'axios';
const BASE_URL = "http://localhost:9999";
const DEFAULT_TIMEOUT = 30000;
export const createClient = (config?: AxiosRequestConfig) => {
const axiosInstance = axios.create({
baseURL: BASE_URL,
timeout: DEFAULT_TIMEOUT,
headers: {
"content-type": "application/json",
},
withCredentials: true,
...config
})
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
return Promise.reject(error);
}
)
return axiosInstance;
}
export const httpClient = createClient();
카테고리 연결해보자.
📄 category.api.ts
import { Category } from "../models/category.model";
import { httpClient } from "./http";
export const fetchCategory = async () => {
const response = await httpClient.get<Category[]>("/category");
return response.data;
}
📄 Header.tsx
function Header() {
const [category, setCategory] = useState<Category[]>([]);
useEffect(() => {
fetchCategory().then((category)=> {
setCategory(category);
});
},[])
return (
<HeaderStyle>
<h1 className="logo">
<Link to="/"><img src={logo} alt="book store"/></Link>
</h1>
<nav className="category">
<ul>
{
category.map((item) => (
<li key={item.category_id}>
<Link to={item.category_id === null ? `/books` : `/books?category_id=${item.category_id}`}>
{item.category_name}</Link>
</li>
))
}
</ul>
....