[WIP] CardList Pagination ๊ตฌํ˜„ํ•˜๊ธฐ

2025. 11. 4. 01:47ใ†Trouble Shooting & Issues/Linkiving

๋ฐ˜์‘ํ˜•

์ฝ”๋“œ์ž‡ ๊ตฌํ˜„๊ณผ์ œ.. api ์—ฐ๊ฒฐ์ด ์ต์ˆ™ํ•˜์ง€ ์•Š์•„์„œ ํž˜๋“ค๋‹ท

 

 

๋ชฉ์ฐจ

1. ์–ด๋–ค ํŒŒ์ผ๋“ค์ด ์žˆ๋Š”๊ฐ€

2. API

    2-1. api.ts

    2-2. products.ts

    2-3. useProducts.ts

3. useProducts.ts

4. Components

    4-1. ItemCard.tsx

    4-2. ItemList.tsx

    4-3. Pagination.tsx

    4-4. AllProducts.tsx

5. Trouble Shooting

    5-1. ๋ฆฌ๋ Œ๋”๋ง ์‹œ ๊นœ๋นก๊ฑฐ๋ฆผ

    5-2. next/image๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ

    5-3. apis/์—๋„ products.ts, type/์—๋„ products.ts์ธ๋ฐ ์ด๋ž˜๋„ ๋˜๋‚˜

    5-4. ํ˜„์žฌ types/products.ts์—๋Š” body์— ๋‹ด๊ธด ๋‚ด์šฉ์ค‘์—์„œ๋„ ๋‹น์žฅ ํ•„์š”ํ•œ ๊ฒƒ๋งŒ ์ž‘์„ฑํ–ˆ๋Š”๋ฐ ๊ทธ๋ƒฅ ?๋ถ™์—ฌ์„œ ์ „๋ถ€ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ์ข‹์„๊นŒ?

    5-5. ProductsResponse ํƒ€์ž…๋ช… ์ด๋Œ€๋กœ๋„ ๊ดœ์ฐฎ์€๊ฐ€

    5-6. UseProductResult์— products, totalCount๋Š” ProductsResponse์˜ ๊ตฌ์กฐ๋ž‘ ๊ฒน์น˜๋Š”๋ฐ ๋‚˜๋ˆ ์„œ ์ž‘์„ฑํ•˜๋Š” ๊ฒŒ ๋งž๋‚˜?


1. ์–ด๋–ค ํŒŒ์ผ๋“ค์ด ์žˆ๋Š”๊ฐ€

ํ˜„์žฌ ๊ตฌํ˜„์„ ์™„๋ฃŒํ•œ ์ƒํƒœ์ด๋‹ค.

๊ทธ๋Ÿฐ๋ฐ ๊ณ„์† ์•ˆ๋˜๋Š” ๊ฒƒ๋“ค์„ ์ˆ˜์ •ํ•˜๋ฉด์„œ ํ•˜๋‹ค๋ณด๋‹ˆ ๋„ˆ๋ฌด.. ๋ชจ๋“  ๊ฒƒ๋“ค์ด ์ •๋ฆฌ๊ฐ€ ์•ˆ๋˜๊ณ  ํ—ท๊ฐˆ๋ฆฌ๋Š” ์ƒํƒœ์—ฌ์„œ ์ •๋ฆฌํ•˜๋Š” ๊ธ€.

๊ธฐ์ˆ  ์Šคํƒ ํƒ€์ž… ์ •์˜ API Components
Next.js
 TypeScript
 Axios
products.ts
 export interface Product {
   id: number;
   name: string;
   price: number;
   favoriteCount: number;
   images: string;
 }
 api.ts
 products.ts
 useProducts.ts
 ItemCard.tsx
 ItemList.tsx
 Pagination.tsx
 AllProducts.tsx

 


2. API

1. api.ts

๊ณตํ†ต API ํด๋ผ์ด์–ธํŠธ ์„ค์ •์„ ํ•˜๋Š” ํŒŒ์ผ์ด๋‹ค.

// 1. axios ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
import axios from 'axios';

// 2. axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
const api = axios.create({
  baseURL: 'https://example.vercel.app',
  timeout: 5000,
  headers: {
    'Content-Type': 'application/json',
  },
});

// 3. ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ ๋“ฑ๋ก
api.interceptors.response.use(
  (res) => res.data,
  (err) => Promise.reject(err),
);

export default api;

// 1. axios ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

axios๋ฅผ ๋ถˆ๋Ÿฌ์™”๋‹ค.

 

// 2. axios ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

axios.create()๋กœ api๋ฅผ axios ์ธ์Šคํ„ด์Šค๋กœ์จ ์ƒ์„ฑํ•˜๊ณ , ์ธ์Šคํ„ด์Šค๊ฐ€ ์ƒ์†ํ•  ๊ธฐ๋ณธ๊ฐ’๋“ค์„ ์ง€์ •ํ–ˆ๋‹ค.

baseURL ์ด ์ธ์Šคํ„ด์Šค๋กœ ์ˆ˜ํ–‰ํ•˜๋Š” ์š”์ฒญ๋“ค์€ ์ƒ๋Œ€๊ฒฝ๋กœ(/)๋งŒ ์ง€์ •ํ•ด๋„ baseURL์„ ๊ฒฐํ•ฉํ•˜์—ฌ ์ตœ์ข… ์š”์ฒญ URL์ด ๊ตฌ์„ฑ๋œ๋‹ค.
timeout  ์š”์ฒญ์ด ์‘๋‹ต์„ ๋ฐ›๊ธฐ๊นŒ์ง€ ์ตœ๋Œ€ 5์ดˆ๋กœ ์„ค์ •ํ•˜์—ฌ, 5์ดˆ๋ฅผ ์ดˆ๊ณผํ•˜๋ฉด ์š”์ฒญ์„ ์ทจ์†Œํ•˜๊ณ  ์—๋Ÿฌ๋ฅผ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค.
headers  ์ด ์ธ์Šคํ„ด์Šค๋กœ ์ „์†ก๋˜๋Š” ๋ชจ๋“  ์š”์ฒญ์˜ ๊ธฐ๋ณธ ํ—ค๋”๋ฅผ ์ง€์ •ํ•œ๋‹ค.
 ์—ฌ๊ธฐ์„œ ์ง€์ •ํ•œ ํ—ค๋”๋Š” ์„œ๋ฒ„์— JSON ๋ฐ”๋””๋ฅผ ๋ณด๋‚ผ ๋•Œ ์ผ๋ฐ˜์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” MIME ํƒ€์ž…์ด๋‹ค.

 

// 3. ์‘๋‹ต ์ธํ„ฐ์…‰ํ„ฐ ๋“ฑ๋ก

์ธํ„ฐ์…‰ํ„ฐ๋Š” ๋ชจ๋“  ์‘๋‹ต(์„ฑ๊ณต/์‹คํŒจ)์— ๋Œ€ํ•ด ํ†ต์ผ๋œ ์ „ํ›„์ฒ˜๋ฆฌ๋ฅผ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•œ๋‹ค. (๊ณตํ†ต ์—๋Ÿฌ ์ฒ˜๋ฆฌ, ์‘๋‹ต ๋ณ€ํ™˜, ์ธ์ฆ ๋งŒ๋ฃŒ ์ฒ˜๋ฆฌ ๋“ฑ)

๋‹น์žฅ์€ ํ•„์š” ์—†์œผ๋‚˜ ์ผ๋‹จ ์ž‘์„ฑํ•ด๋‘ฌ๋ดค๋‹ค.

(res)  ์‘๋‹ต ์„ฑ๊ณต ์ฝœ๋ฐฑ
์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์‘๋‹ต ๊ฐ์ฒด(res) ๋Œ€์‹  ๊ทธ ์•ˆ์˜ data ํ•„๋“œ๋งŒ ๋ฐ˜ํ™˜ํ•˜๋„๋ก(res.data) ํ•œ๋‹ค.
axios์˜ ํ‘œ์ค€ ์‘๋‹ต ๊ฐ์ฒด๋Š” { data, status, headers, ... } ํ˜•ํƒœ์ด๋‚˜, ๋Œ€๋ถ€๋ถ„์˜ ์•ฑ์€ data๋งŒ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
(err)  ์‘๋‹ต ์‹คํŒจ ์ฝœ๋ฐฑ
์‘๋‹ต ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์ธํ„ฐ์…‰ํ„ฐ์—์„œ Promise.reject()๋กœ ์—๋Ÿฌ๋ฅผ ์žฌ์ „๋‹ฌํ•œ๋‹ค.
์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์ธํ„ฐ์…‰ํ„ฐ ์ดํ›„์˜ try, catch์—์„œ ์—๋Ÿฌ๋ฅผ ์ผ๊ด€๋˜๊ฒŒ ์žก์„ ์ˆ˜ ์žˆ๋‹ค.
Promise.reject(err)๋Š” Rejected Promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ‘œ์ค€ JS ๋ฐฉ์‹์ด๋‹ค.

 

 

2. products.ts

products ๊ด€๋ จ API ์š”์ฒญ ํ•จ์ˆ˜๋ฅผ ์ •์˜ํ•œ ํŒŒ์ผ์ด๋‹ค.

// 1. ์ •์˜ํ•œ ํƒ€์ž… ๋ฐ api ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
import { Product } from '@/types/product';
import api from './api';

const END_POINT = '/products/';

// 2. ์‘๋‹ตํ˜•ํƒœ ํƒ€์ž… ์ •์˜
interface ProductsResponse {
  list: Product[];
  totalCount: number;
}

// 3. getProductsList ํ•จ์ˆ˜ ์„ ์–ธ 
export const getProductsList = (option = {}):Promise<ProductsResponse> => {
  return api.get(END_POINT, {
    params: option
  });
}
// 4. getTargetProduct ํ•จ์ˆ˜ ์„ ์–ธ  
export const getTargetProduct = (productId:number)=>{
  return api.get(`${END_POINT}${productId}/`);
}

// 1. ์ •์˜ํ•œ ํƒ€์ž… ๋ฐ api ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

์ •์˜ํ•œ Product ํƒ€์ž…๊ณผ api๋ฅผ ๋ถˆ๋Ÿฌ์˜จ๋‹ค.

๋˜ํ•œ API ๊ฒฝ๋กœ(์—”๋“œํฌ์ธํŠธ)๋ฅผ ์ƒ์ˆ˜๋กœ ์„ ์–ธํ–ˆ๋‹ค.

 

// 2.  ์‘๋‹ตํ˜•ํƒœ ํƒ€์ž… ์ •์˜

getProductsList๊ฐ€ ๋ฐ˜ํ™˜ํ•  ๋ฐ์ดํ„ฐ์˜ ํƒ€์ž…์„ ์ •์˜ํ–ˆ๋‹ค.

  • list: Product[] → Product ๊ฐ์ฒด ๋ฐฐ์—ด๋กœ, ์‹ค์ œ API์—์„œ ์ƒํ’ˆ ๋ชฉ๋ก์ด ๋“ค์–ด์˜ค๋Š” ํ•„๋“œ
  • totalCount : number → ์ „์ฒด ์ƒํ’ˆ ์ˆ˜ (ํŽ˜์ด์ง•์„ ์œ„ํ•ด ํ•„์š”)

์‘๋‹ตํ˜•ํƒœ ํƒ€์ž…์€ swagger ui์—์„œ ํ™•์ธํ•˜๊ณ  ์ž‘์„ฑํ–ˆ๋‹ค.

"list": [ ... ], "totalCount"๋ฅผ ๋ฑ‰์Œ

 

 

// 3. getProductsList ํ•จ์ˆ˜ ์„ ์–ธ 

option = {} ๊ธฐ๋ณธ๊ฐ’์ด ๋นˆ ๊ฐ์ฒด์ธ ๋งค๊ฐœ๋ณ€์ˆ˜๋กœ, ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š”๋‹ค.
Swagger์— ๋”ฐ๋ฅด๋ฉด page, pageSize, orderBy, keyword ์ด๋ ‡๊ฒŒ ๋„ค ๊ฐ€์ง€๋ฅผ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
๋ฐ˜ํ™˜ํ˜• ํƒ€์ž…
Promise<ProductsResponse>
์ด ํ•จ์ˆ˜๊ฐ€ ProductsResponse ํƒ€์ž…์˜ ๊ฐ’์„ ํฌํ•จํ•œ Promise๋ฅผ ๋ฐ˜ํ™˜ํ•  ๊ฒƒ์„ ์„ ์–ธํ•œ๋‹ค.
๋˜ํ•œ Promise๋ฅผ ๋ถ™์˜€์œผ๋ฏ€๋กœ ๋น„๋™๊ธฐ์  ๋™์ž‘์„ ํ•จ๋„ ๋‚˜ํƒ€๋‚ธ๋‹ค.
return๊ฐ’  api.get()์œผ๋กœ GET ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
์ฒซ๋ฒˆ์งธ ์ธ์ˆ˜: ์ตœ์ข… URL๋กœ, api์˜ baseURL๊ณผ ๊ฒฐํ•ฉ๋˜์–ด https://.../products/๊ฐ€ ์ตœ์ข… ํ˜•ํƒœ์ด๋‹ค.
๋‘๋ฒˆ์งธ ์ธ์ˆ˜: ์ฟผ๋ฆฌ์ŠคํŠธ๋ง์œผ๋กœ ๋ณ€ํ™˜๋  ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์ „๋‹ฌํ•œ๋‹ค. 
   { params: { page: 2 } } ํ˜•ํƒœ๋ผ๋ฉด, ์ž๋™์œผ๋กœ ?page=2๋กœ ๋ณ€ํ™˜๋œ๋‹ค.

 

 

// 4. getTargetProduct ํ•จ์ˆ˜ ์„ ์–ธ 

productId: number๋ฅผ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค.

์ธ์ž๋กœ ๋ฐ›์€ productId๋Š” api.get()์œผ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ ํ…œํ”Œ๋ฆฟ ๋ฆฌํ„ฐ๋Ÿด๋กœ ๊ตฌ์„ฑํ•˜์—ฌ ๋ณด๋‚ธ๋‹ค.

ํ˜„์žฌ๋กœ์จ๋Š” ์•„์ง ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์„ ์–ธ๋งŒ ํ•ด๋‘” ๊ฑฐ์—ฌ์„œ ๋ฐ˜ํ™˜ํƒ€์ž…์„ ๋ช…์‹œํ•˜์ง€ ์•Š์•˜๋‹ค. (์ถ”ํ›„ ๋ช…์‹œ ํ•„์š”)

 

 


3. useProducts.ts

useProducts.ts๋Š” ์–‘์ด ์ข€ ๋งŽ์•„์„œ API์— ์•ˆ๋„ฃ๊ณ  ๋”ฐ๋กœ ๋บ๋‹ค.

์ฝ”๋“œ๊ฐ€ ์ข€ ๊ธธ์–ด์„œ ๊ทธ๋ƒฅ ๋‚˜๋ˆ ์„œ ์ˆœ์„œ๋Œ€๋กœ ์ •๋ฆฌํ•ด๋ณด๊ฒ ๋‹ค.

1. ํ•„์š”ํ•œ ํŒŒ์ผ๋“ค ๋ถˆ๋Ÿฌ์˜ค๊ธฐ

"use client";

import { getProductsList } from "@/apis/products";
import { useState, useEffect, useRef } from "react";
import { Product } from "@/types/product";

ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋™์ž‘ํ•˜๋„๋ก ์ง€์ •ํ•œ๋‹ค. ํ›… ๋‚ด๋ถ€์—์„œ ๋ธŒ๋ผ์šฐ์ € ์ „์šฉ api( useState, useEffect .. )๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

๋˜ํ•œ ํ›…์—์„œ ํ•„์š”ํ•œ ํŒŒ์ผ๋“ค์„ ๋ถˆ๋Ÿฌ์™”๋‹ค.

  • getProductsList : ๋ฐฑ์—์„œ ์ƒํ’ˆ ๋ชฉ๋ก์„ GETํ•˜๋Š” API ํ˜ธ์ถœ ๋ž˜ํผ
  • use@@ : ์ƒํƒœ, ์‚ฌ์ด๋“œ์ดํŽ™ํŠธ, ๋ ˆํผ๋Ÿฐ์Šค ๊ด€๋ฆฌ
  • Product : ์ƒํ’ˆ ๊ฐ์ฒด ๊ตฌ์กฐ

2. ํ›… ํŒŒ๋ผ๋ฏธํ„ฐ ํƒ€์ž… ์ •์˜ (ํ›…ํ•œํ…Œ ๋จน์ผ ๊ฑฐ)

interface UseProductsParams {
  page?: number;
  pageSize?: number;
  orderBy?: "recent" | "favorite";
  search?: string;
}

ํ›…์— ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•  ํƒ€์ž…์„ ์ •์˜ํ–ˆ๋‹ค. Swagger ๋ฌธ์„œ์— ๋”ฐ๋ผ ์ž‘์„ฑํ–ˆ๋‹ค.

swagger

3. ํ›… ๋ฐ˜ํ™˜ ๊ฐ์ฒด ํƒ€์ž… ์ •์˜ (ํ›…์ด ๋ฑ‰์„ ๊ฑฐ)

interface UseProductResult {
  products: Product[];
  totalCount: number;
  loading: boolean;
  error: Error | null;
}

ํ›…์ด ๋ฐ˜ํ™˜ํ•  ๊ฐ์ฒด์˜ ํƒ€์ž…์„ ์ •์˜ํ–ˆ๋‹ค.

๊ฐ์ฒด ๋ฐ”๋””์ธ list, totalCount๋ฅผ ํฌํ•จํ•ด์„œ loading, error๋„ ์ถ”๊ฐ€๋กœ ์ •์˜ํ–ˆ๋‹ค.

 

4. useProducts ์„ ์–ธ

export const useProducts = ({ 
  page,
  pageSize,
  orderBy,
  search
} : UseProductsParams): UseProductResult => { ...

UserProductsParams ํƒ€์ž…์„ ๋ฐ›๋„๋ก ์„ ์–ธํ–ˆ์œผ๋ฉฐ, ๋ฐ˜ํ™˜๊ฐ’์œผ๋กœ UseProductResult ํƒ€์ž…์„ ๊ฐ€์ง€๋„๋ก ํ–ˆ๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ๋“ค์˜ ์ดˆ๊ธฐ๊ฐ’์„ ์„œ๋ฒ„์—์„œ ๋ณด์žฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ๊ฐ’ ์ง€์ •์„ ํ•˜์ง€ ์•Š์•˜๋‹ค.

 

 

5. ๋ณ€์ˆ˜ ๋ฐ setter ์„ ์–ธ

const [products, setProducts] = useState<Product[]>([]);
const [totalCount, setTotalCount] = useState(0);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);

 

products, totalCount ⋅ ์‹ค์ œ ๋ณด์—ฌ์ค„ ๋ฐ์ดํ„ฐ์™€ ์ด ๊ฐœ์ˆ˜
loading  ์ดˆ๊ธฐ ๋กœ๋”ฉ(์ดˆ๊ธฐ ๋งˆ์šดํŠธ ์‹œ) ์—ฌ๋ถ€
true๋กœ ์ดˆ๊ธฐํ™”ํ•˜์—ฌ ๋งˆ์šดํŠธ ์‹œ ๋กœ๋“œ๊ฐ€ ์žˆ์Œ์„ ๋ช…ํ™•ํžˆ ํ•จ
error ⋅ ์—๋Ÿฌ ๊ฐ์ฒด

 

 

6. fetchProducts ์ •์˜

fetchProducts๋Š” try, catch, finally๋ฅผ ๊ฐ€์ง„๋‹ค. ๊ฐ ํŒŒํŠธ๋ณ„๋กœ ๋‚˜๋ˆ ์„œ ์ •๋ฆฌํ•˜์ž.

 

try

// fetchProducts() : ์ƒํ’ˆ ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
  const fetchProducts = async (initial = false) => {
    if (initial) setLoading(true);
    setError(null);
    try {
      const res = await getProductsList({
        page,
        pageSize,
        orderBy,
        search,
      });

      // ๋ฐ์ดํ„ฐ ๋งคํ•‘
      const list = (res.list ?? []).map(
        (p: Product): Product => ({
          id: p.id,
          name: p.name,
          price: p.price,
          favoriteCount: p.favoriteCount,
          images: p.images?.[0],
        }),
      );
  // ๋ฐ์ดํ„ฐ ์ƒํƒœ ์—…๋ฐ์ดํŠธ
  setProducts(list ?? []);
  setTotalCount(res.totalCount ?? 0);
}
  • fetchProducts๋Š” ์‹ค์ œ API ํ˜ธ์ถœ์„ ๋‹ด๋‹นํ•œ๋‹ค.
    initial ํ”Œ๋ž˜๊ทธ๋กœ ์ดˆ๊ธฐ๋กœ๋“œ(true)์ธ์ง€, ์•„๋‹ˆ๋ฉด ์žฌ์š”์ฒญ(false)์ธ์ง€ ๊ตฌ๋ถ„ํ•œ๋‹ค.
  • setError(null)๋กœ ์š”์ฒญ ํ•˜๊ธฐ ์ „์— ๊ธฐ์กด ์—๋Ÿฌ๋ฅผ ์ดˆ๊ธฐํ™” ํ•œ๋‹ค.

getProductsList()

  • axios ์š”์ฒญํ•˜๋Š” ํ•จ์ˆ˜์ธ getProductsListfh API๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.
  • ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ฟผ๋ฆฌ(page, pageSize, orderBy, search)๋“ค์„ ์ „์†กํ•œ๋‹ค.

mapped

  • getProductsList๋กœ ํ˜ธ์ถœํ•ด์„œ ์‘๋‹ต๋ฐ›์€ ๊ฐ’์ด res์— ์žˆ๋‹ค. res์— ๋“ค์–ด์žˆ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์šฐ๋ฆฌ๊ฐ€ ์‚ฌ์šฉํ•  ๋ณ€์ˆ˜์— ๋งคํ•‘ํ•ด์ค˜์•ผ ํ•œ๋‹ค.
  • ๋ฐ”๋””๋ฅผ ๋ณด๋ฉด "list" ๋ฐฐ์—ด ์•ˆ์— ๋งคํ•‘ํ•  ๋ฐ์ดํ„ฐ๋“ค์ด ๋“ค์–ด์žˆ์—ˆ์œผ๋ฏ€๋กœ res.list(๋น„์—ˆ๋‹ค๋ฉด ๋นˆ ๋ฐฐ์—ด[])์˜ ๋ฐ์ดํ„ฐ๋ฅผ Product์— ๋งคํ•‘ํ•œ๋‹ค.

 

catch, finally

catch (err) {
  if (err instanceof Error) setError(err);
  else setError(new Error("Unknown error"));
} finally {
  setLoading(false);
}
};

catch๋กœ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ํ•˜๊ณ , finally๋กœ ์š”์ฒญ ์™„๋ฃŒ ์‹œ loading์„ false๋กœ ์„ธํŒ…ํ•œ๋‹ค.

 

7. ๋งˆ๋ฌด๋ฆฌ

  // initialParams์˜ ๊ฐ ๊ฐ’์ด ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค fetch
  useEffect(() => {
    fetchProducts();
  }, [page, pageSize, orderBy, search]);

  // refetch
  const refetch = async () => {
    await fetchProducts();	// refetch๋Š” false
  };

  return { products, totalCount, loading, error };
};

 

useEffect()๋กœ ์œ„์—์„œ ์ •์˜ํ•œ fetchProducts๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ’์ด ๋‹ฌ๋ผ์งˆ ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœํ•œ๋‹ค.

 

๋ฆฌ๋ Œ๋”๋ง์„ ์œ„ํ•œ refetch๋ฅผ ์ •์˜ํ•ด์„œ ๋ฆฌํ„ด๊ฐ’์— ํฌํ•จ์‹œํ‚จ๋‹ค.

 

 


4. Components

1. ItemCard.tsx

๋ณธ๋ฌธ

2. ItemList.tsx

๋ณธ๋ฌธ

3. Pagination.tsx

๋ณธ๋ฌธ

4. AllProducts.tsx

๋ณธ๋ฌธ

 

 


5. TroubleShooting

1. ๋ฆฌ๋ Œ๋”๋ง์‹œ ๊นœ๋นก๊ฑฐ๋ฆผ

์ด์ „์—๋Š” try๋ฌธ์„ ์‹œ์ž‘ํ•  ๋•Œ ํ•ญ์ƒ setLoading(true)๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ๋˜์–ด์žˆ์—ˆ๋‹ค.

์ด ๋•Œ์˜ React ๋™์ž‘ ์ˆœ์„œ๋ฅผ ์‚ดํŽด๋ณด์ž.

  1. ์ฒ˜์Œ ํŽ˜์ด์ง€ ๋ Œ๋”(products๊ฐ€ ๋น„์–ด์žˆ์Œ. ๋˜ํ•œ loading๋„ true๋กœ ์ดˆ๊ธฐํ™”๋˜์–ด ์žˆ์Œ)
  2. fetchProducts() ํ˜ธ์ถœํ•˜์—ฌ ๋ฐ์ดํ„ฐ ์š”์ฒญ → setLoading(true)
    → React๊ฐ€ ๋ฆฌ๋ Œ๋”๋ง
    → ํ™”๋ฉด์—์„œ ๊ธฐ์กด ์นด๋“œ DOM์„ ์ œ๊ฑฐ (๋นˆ ์ƒํƒœ)
  3. ๋ฐ์ดํ„ฐ ๋„์ฐฉ → setProducts(list) + setLoading(false)
    React๊ฐ€ ๋„์ฐฉํ•œ ๋ฐ์ดํ„ฐ๋กœ ๋ฆฌ๋ Œ๋”๋ง
    ์นด๋“œ UI๊ฐ€ ์ƒˆ๋กœ ๊ทธ๋ ค์ง

ํ™”๋ฉด์˜ ์ฒซ ๋ Œ๋”๋ง์ด ์™„๋ฃŒ๋œ ํ›„ loading์€ finally๋ฌธ์—์„œ false ์ƒํƒœ๊ฐ€ ๋œ๋‹ค.

ํ•˜์ง€๋งŒ ํŽ˜์ด์ง€๋„ค์ด์…˜์œผ๋กœ ๋ฆฌ๋ Œ๋”๋ง์„ ํ•  ๋•Œ๋งˆ๋‹ค loading์„ true๋กœ ๋ฐ”๊ฟ€๋•Œ๋งˆ๋‹ค React๊ฐ€ ๊ธฐ์กด DOM์„ ์ดˆ๊ธฐํ™”ํ•˜๊ณ  ์žฌ๋ Œ๋”๋ง์„ ํ•˜๋ฉฐ ๊นœ๋นก์ž„์ด ๋ฐœ์ƒํ•˜๊ฒŒ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

 

๊ทธ๋ ‡๋‹ค๋ฉด if (initial) setLoading(true)๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์šฐ์„  ๊ฐ€์žฅ ์ฒ˜์Œ ํŽ˜์ด์ง€๋ฅผ ๋กœ๋“œํ–ˆ์„ ๋•Œ๋Š” loading์ด true์ผ ๊ฒƒ์ด๋‹ค. (์ฒ˜์Œ์— ๊ทธ๋ ‡๊ฒŒ ์ดˆ๊ธฐํ™”ํ–ˆ๊ธฐ ๋•Œ๋ฌธ)

ํ•˜์ง€๋งŒ ๊ทธ ๋’ค๋กœ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ๋ฉด loading์€ false์ƒํƒœ๊ฐ€ ๋œ๋‹ค.

 

  1. ์ดˆ๊ธฐ ๋ Œ๋” (loading = true)
  2. ๋ฐ์ดํ„ฐ ๋„์ฐฉ → setProducts(list) + setLoading(false)
    ์นด๋“œ UI ๋ Œ๋”๋ง
  3. ํŽ˜์ด์ง€๋„ค์ด์…˜ ๋“ฑ์œผ๋กœ refetch → initial = false
    setLoading(true)๊ฐ€ ํ˜ธ์ถœ๋˜์ง€ ์•Š์Œ
    ๊ธฐ์กด ์นด๋“œ UI ๊ทธ๋Œ€๋กœ ์œ ์ง€ํ•˜๊ณ  ๋ฐ์ดํ„ฐ๋งŒ ๊ต์ฒด
    React๋Š” DOM ์ผ๋ถ€๋งŒ ๊ฐฑ์‹ ํ•˜์—ฌ ๊นœ๋นก์ž„์ด ์—†์Œ

์ด๋ ‡๊ฒŒ loading ์ƒํƒœ๋ฅผ ๊ฐ•์ œ๋กœ true๋กœ ๋Œ๋ฆฌ์ง€ ์•Š๊ฒŒํ•˜์—ฌ ๊นœ๋นก์ž„์„ ์ง€์› ๋‹ค.

 

โ—๊ทธ๋Ÿฐ๋ฐ ์ด๋ ‡๊ฒŒ loading ์ƒํƒœ๋ฅผ ์ง€์›Œ๋„ ๊ดœ์ฐฎ์„๊นŒ??

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ฐœ๋ฐœ์ž๋„๊ตฌ์—์„œ ๋„คํŠธ์›Œํฌ์— throttling์„ ๊ฑธ์—ˆ์„ ๋•Œ ์‚ฌ์šฉ์ž๊ฐ€ ๋ฐ์ดํ„ฐ์˜ ๋กœ๋”ฉ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์—†๋‹ค.

ํŽ˜์ด์ง€๋„ค์ด์…˜์œผ๋กœ ํŽ˜์ด์ง€๋ฅผ ๋„˜๊ฒจ๋„ ์•„๋ฌด๋Ÿฐ ์•ก์…˜ ์—†์ด ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ•  ๋•Œ๊นŒ์ง€ ์ด์ „ ๋ฐ์ดํ„ฐ๋งŒ ๋„์šฐ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

โœ… ์ด ๋ถ€๋ถ„์€ ์‚ฌ์‹ค ์ด์ „์— ๋‹ค๋ฅธ ๊ณผ์ œ ํ”Œ์ ์—์„œ ๋‹ค๋ฃฌ ์ ์ด ์žˆ๋‹ค.

์—ฌ๊ธฐ์„œ๋Š” ๋กœ๋”ฉ์„ ์•„์˜ˆ ์“ฐ์ง€๋„ ์•Š์•˜๊ตฐ

skeleton ์ž‘์—…์„ ์ถ”๊ฐ€๋กœ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

์ „์— ์นด์นด์˜ค ๋ธ”๋กœ๊ทธ์—์„œ ์Šค์ผˆ๋ ˆํ†ค์„ ๋ฌด์กฐ๊ฑด์ ์œผ๋กœ ๋„์šฐ๋ฉด ์˜คํžˆ๋ ค ๊นœ๋นก์ด๋Š” ๋А๋‚Œ์„ ์ฃผ์–ด ํ”ผ๋กœ๊ฐ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ๋ณด๊ณ  ์ž‘์„ฑํ–ˆ๋˜ ๊ฒƒ์ด๋‹ค.

์ปดํฌ๋„ŒํŠธ ๋งˆ์šดํŠธ ํ›„ 0.2์ดˆ๊ฐ„์€ skeleton์„ ๋ณด์ด์ง€ ์•Š์œผ๋‚˜, 0.2์ดˆ๊ฐ€ ๋„˜์œผ๋ฉด ์Šค์ผˆ๋ ˆํ†ค์„ ๋ณด์ธ๋‹ค.

์Šค์ผˆ๋ ˆํ†ค์„ ๋„์šฐ๊ณ  ๊ธฐ๋‹ค๋ฆฌ๋Š”๋™์•ˆ ๋ฐ์ดํ„ฐ๊ฐ€ ๋กœ๋“œ๋˜๋ฉด ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ณด์ด๋Š” ๊ฑฐ๋‹ค.

 

ํ›…์—์„œ๋Š” ์Šค์ผˆ๋ ˆํ†ค์„ ๋ณด์ผ์ง€ ์—ฌ๋ถ€๋งŒ ์•Œ๋ ค์ฃผ๊ณ  ๋ณด์—ฌ์ฃผ๋Š”๊ฑด UI์—์„œ ๋งก๊ฒŒ๋˜๋Š”๋ฐ,

๊ทธ๊ฑธ ์ƒ๊ฐํ•˜๋ฉด ์‚ฌ์‹ค loading์ด ๋‹ค์‹œ๋Š” true๋กœ ๋  ์ˆ˜ ์—†๋Š” ์ € ์ฝ”๋“œ์˜ ๊ตฌ์กฐ๋„ ๋ฌธ์ œ๊ฐ€ ์žˆ๋‹ค.

(์ € ๋•Œ๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชฝ๋•… ๋ถˆ๋Ÿฌ์™€์„œ ๋ฌธ์ œ๊ฐ€ ๋ฐœ๊ฒฌ๋˜์ง€ ์•Š์•˜๋‹ค.)

 

์ด ๋ถ€๋ถ„์€ ์Šค์ผˆ๋ ˆํ†ค์„ ๊ตฌํ˜„ํ•œ ํ›„, ์ฒดํฌํ•˜๋ฉด์„œ ๋‹ค์‹œ ๋‹ค๋ค„๋ด์•ผ๊ฒ ๋‹ค. ์ผ๋‹จ ์ง€๊ธˆ์€ ๊นœ๋นก์ž„์„ ํ•ด๊ฒฐํ•˜๊ธด ํ–ˆ๋‹ค.

 

2. next/image๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ

 

 

 

 

์ฐธ๊ณ 

๋ฐ˜์‘ํ˜•