/Banana

๐ŸŒ ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ํŒจ์…˜ ์ œํ’ˆ์„ ๋‚˜๋ˆ„๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ

Primary LanguageJavaScript

BANANA : ๋ฐ”๋กœ ๋‚˜๋ˆ„๊ณ  ๋‚˜๋ˆ”๋ฐ›์ž



๐ŸŒ BANANA๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์ง€๋งŒ ๊ฐ€์น˜๊ฐ€ ์žˆ๋Š” ํŒจ์…˜ ์ œํ’ˆ์„ ๋‚˜๋ˆ„๋Š” ์ปค๋ฎค๋‹ˆํ‹ฐ ์›น์‚ฌ์ดํŠธ์ž…๋‹ˆ๋‹ค.

0. ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

๐ŸŒ 2์ธ ํŒ€ ํ”„๋กœ์ ํŠธ ( 2023.03 ~ (ํ˜„์žฌ develop/back branch ์—์„œ ์ž‘์—…์ค‘)
๐ŸŒ ํ”„๋กœ์ ํŠธ ๋ชฉํ‘œ : ์žฌํ™œ์šฉ & ์žฌ์‚ฌ์šฉ ์ธ์‹ ์ฆ๋Œ€ ๋ฐ ํ™˜๊ฒฝ ์นœํ™”์ ์ธ ์†Œ๋น„๋ฌธํ™” ์กฐ์„ฑ

 ์˜ท์€ ์šฐ๋ฆฌ ์ƒํ™œ์—์„œ ํ•„์ˆ˜์ ์ธ ์š”์†Œ ์ค‘ ํ•˜๋‚˜์ด์ง€๋งŒ, ์˜ท ์‚ฐ์—…์€ ๋งŽ์€ ํ™˜๊ฒฝ ๋ฌธ์ œ๋ฅผ ์•ผ๊ธฐํ•ฉ๋‹ˆ๋‹ค. 
 ์„ฌ์œ  ์ œ์กฐ ๊ณผ์ •์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๋Œ€๋Ÿ‰์˜ ๋ฌผ ์‚ฌ์šฉ๊ณผ ํ™”ํ•™ ๋ฌผ์งˆ ๋ฐฐ์ถœ, ์˜ท ์ƒ์‚ฐ๊ณผ ์†Œ๋น„๋กœ ์ธํ•œ ํ๊ธฐ๋ฌผ์˜ ์ฆ๊ฐ€๋Š” ํ™˜๊ฒฝ์— ์‹ฌ๊ฐํ•œ ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. 
 ์ด์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ… ์ค‘ ํ•˜๋‚˜๋Š” "์žฌํ™œ์šฉ"๊ณผ "์žฌ์‚ฌ์šฉ"์ž…๋‹ˆ๋‹ค. 
 ์ž…์ง€ ์•Š์€ ์˜ท์„ ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๊ณต์œ ํ•˜๊ณ  ๋‚˜๋ˆ”ํ•˜๋Š” ๊ฒƒ์€ ํ™˜๊ฒฝ ์นœํ™”์ ์ธ ์†Œ๋น„ ๋ฌธํ™”๋ฅผ ์กฐ์„ฑํ•˜๊ณ , ์ž์›์˜ ํšจ์œจ์ ์ธ ์‚ฌ์šฉ์„ ์ด‰์ง„ํ•  ์ˆ˜ ์žˆ๋Š” ์ข‹์€ ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค.
 
 BANANA ๋‚˜๋ˆ” ์ปค๋ฎค๋‹ˆํ‹ฐ๋กœ ํ™œ์šฉํ•จ์œผ๋กœ์จ ํ™˜๊ฒฝ์— ๋Œ€ํ•œ ์ธ์‹๊ณผ ๊ด€์‹ฌ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 
 ๋ˆ„๊ตฌ๋‚˜ ์ž์‹ ์ด ์ž…์ง€ ์•Š์€ ์˜ท์„ ๊ธฐ๋ถ€ํ•˜๊ณ , ํ•„์š”๋กœ ํ•˜๋Š” ์‚ฌ๋žŒ๋“ค์—๊ฒŒ ๋‚˜๋ˆ”ํ•  ์ˆ˜ ์žˆ๋Š” ํ”Œ๋žซํผ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

1. Contributor

ํ™ฉ์ง€๋‚˜ ๊น€ํ˜„์ง€
ํ™ฉ์ง€๋‚˜ ๊น€ํ˜„์ง€
@hwangJN @hyeonjy
wlsk401@gmail.com hg024246@gmail.com

2. ์‚ฌ์šฉ ๊ธฐ์ˆ 

React reactquery styledcomponents reacthookform
nodedotjs express mysql

โœ๏ธ ์‚ฌ์šฉ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

  • Styled-components : ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ์Šคํƒ€์ผ๋ง
  • React Query : ๋ฐ์ดํ„ฐ ์ƒํƒœ๊ด€๋ฆฌ (useQuery, useMutation)
  • Recoil : ๋กœ๊ทธ์ธ ์ƒํƒœ ๊ด€๋ฆฌ
  • jsonwebtoken : ๋กœ๊ทธ์ธ ์œ ์ € ๊ถŒํ•œ ๋ถ€์—ฌ
  • React-device-detect : BrowserView ์™€ MobileView ๋กœ ๋‚˜๋ˆ„์–ด ์ž‘์—…
  • React-hook-form : form ์„ ๊ตฌํ˜„ ๋ฐ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  • React Swiper & Slick : ์ด๋ฏธ์ง€ Slider ๊ตฌํ˜„
  • React-loading-skeleton : ๋กœ๋”ฉ UI&UX ๊ฐœ์„ 
  • React-js-pagination : ํŽ˜์ด์ง€๋„ค์ด์…˜

3. ๊ธฐํš ๋ฐ ์„ค๊ณ„

0. ๊ธฐํš ๋ฐ ๋””์ž์ธ ์„ค๊ณ„

๐Ÿ”—FIGMA

1. ERD ์„ค๊ณ„

2. API ์ž„์‹œ ์„ค๊ณ„ (์ˆ˜์ •ing)

3 . ๊ตฌํ˜„ ๊ธฐ๋Šฅ

1. ๋กœ๊ทธ์ธ & ํšŒ์›๊ฐ€์ž…

  • OAuth 2.0 ๊ธฐ๋ฐ˜ ์†Œ์…œ ๋กœ๊ทธ์ธ ๋ฐ React-hook-form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์ž…๋‹ˆ๋‹ค.(์นด์นด์˜ค & ๊ตฌ๊ธ€)
  • JWT(+Refresh token)์„ ํ†ตํ•œ ์œ ์ € ๊ถŒํ•œ ๋ถ€์—ฌํ–ˆ์œผ๋ฉฐ Recoil ์„ ํ†ตํ•ด ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

2. ๋‚˜๋ˆ” ๊ธ€ ์ž‘์„ฑ & ์ˆ˜์ •

  • React-hook-form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ํ™œ์šฉํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ ํ›„ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๊ฐ€ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.
  • ์ด๋ฏธ์ง€ ํŒŒ์ผ ์ถ”๊ฐ€ & ์‚ญ์ œ & ๋Œ€ํ‘œ์‚ฌ์ง„์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


3. ๋‚˜๋ˆ” ๊ธ€ ํŽ˜์ด์ง€ & ์ด๋ฏธ์ง€ ์Šฌ๋ผ์ด๋”

  • React-Swiper & Slick Library๋ฅผ ์‚ฌ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค.
  • ์ด๋ฏธ์ง€๋ฅผ ํฌ๊ฒŒ ๋ณด๋Š” ๊ฒฝ์šฐ modal ํ˜•ํƒœ๋กœ ๋‚˜ํƒ€๋‚˜๋„๋ก ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.


4. ์œ„์‹œ๋ฆฌ์ŠคํŠธ ์ถ”๊ฐ€ & ๋ชฉ๋ก ํ™•์ธ

  • React Query ์™€ Optimistic Update๋ฅผ ํ™œ์šฉํ•˜์˜€์Šต๋‹ˆ๋‹ค
  • ์„œ๋ฒ„ ์š”์ฒญ ์™„๋ฃŒ ์ „ UI๋ฅผ ์—…๋ฐ์ดํŠธ๋ฅผ ํ†ตํ•ด ๋น ๋ฅธ ๋ฐ˜์‘์†๋„๋ฅผ ๋Š๋‚„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


5. ๋‚˜์˜ ๋‚˜๋ˆ”๋ชฉ๋ก & ์Šค์ผˆ๋ ˆํ†ค ์ปดํฌ๋„ŒํŠธ

  • ๋กœ๊ทธ์ธ ์œ ์ €์˜ ๋งˆ์ดํŽ˜์ด์ง€์—์„œ ๋‚˜๋ˆ” ๋ชฉ๋ก์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  • React-loading-skeleton library๋ฅผ ํ†ตํ•ด ์Šค์ผˆ๋ ˆํ†ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.


6. ๊ธ€ ์ž‘์„ฑ์ž์˜ ํ”„๋กœํ•„ ํ™•์ธ

  • ๋‹ค๋ฅธ ์œ ์ €์˜ ๋‚˜๋ˆ” ๋ชฉ๋ก, ์œ ์ €๊ฐ€ ๋ฐ›์€ ํ›„๊ธฐ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


7. ๊ฒŒ์‹œ๋ฌผ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ

  • ๊ฒ€์ƒ‰ ํ‚ค์›Œ๋“œ๊ฐ€ ์ œ๋ชฉ ํ˜น์€ ๋‚ด์šฉ์— ํฌํ•จ๋˜๋Š” ๊ฒŒ์‹œ๋ฌผ์„ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


8. ์ตœ์‹ ์ˆœ, ์กฐํšŒ์ˆœ ๋“ฑ ๊ฒŒ์‹œ๋ฌผ ์ •๋ ฌ ๊ธฐ๋Šฅ (์นดํ…Œ๊ณ ๋ฆฌ)

  • ๋“ฑ๋ก์ˆœ, ์กฐํšŒ์ˆœ ๋“ฑ ๊ฒŒ์‹œ๋ฌผ์„ ์ •๋ ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


9. (์˜ˆ์ •) WebSocket์„ ์ด์šฉํ•œ ์ฑ„ํŒ… ๊ธฐ๋Šฅ ๊ตฌํ˜„

4. ๊ฐœ๋ฐœ ๊ธฐ๋ก

๐Ÿ“‹ ๊ฒŒ์‹œ๋ฌผ(post) ์ˆ˜์ • - 1. ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ฐ€์ ธ์˜ค๊ธฐ

  • ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ ์‹œ์—๋Š” ๋กœ์ปฌ์—์„œ ์—…๋กœ๋“œํ•œ ์ด๋ฏธ์ง€์˜ ๋ฏธ๋ฆฌ๋ณด๊ธฐ๋ฅผ ์œ„ํ•ด createObjectURL ๋กœ ์ ‘๊ทผํ–ˆ์ง€๋งŒ, ๊ฒŒ์‹œ๋ฌผ ์ˆ˜์ •์‹œ์—๋Š” ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ „๋‹ฌ๋ฐ›์€ base64 ํƒ€์ž…์˜ ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ๋ฅผ URL๋กœ ๋ณ€ํ™˜ํ•˜๋Š”๋ฐ ์‹คํŒจํ–ˆ๋‹ค.
  • ๋ฐฉ๋ฒ•์„ ๋ฐ”๊ฟ” FileReader ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋กœ์ปฌ์—์„œ ์—…๋กœ๋“œ๋˜๋Š” ์ด๋ฏธ์ง€๋ฅผ base64 ํƒ€์ž…๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

๊ฐœ์„  ์ด์ „ - ๊ธฐ์กด ์ด๋ฏธ์ง€๊ฐ€ ๋œจ์ง€ ์•Š์Œ

   // ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋„์šฐ๊ธฐ
   function base64ToImgUrl(base64) {
      const blob = new Blob([base64], { type: 'image/jpeg' });
      const url = URL.createObjectURL(blob);
      return url;
    }
    useEffect(() => {
        if (state) {
          //....
          let imgurls = [];
          for (let i = 0; i < state.item.imgs.length; i++) {
            imgurls.push(base64ToImgUrl(state.item.imgs[i].data)); // base64 ๋ฐ์ดํ„ฐ - > ObjectURL
          }
          setImgURLs(imgurls);
       }
    }, [state]);

   // ๋กœ์ปฌ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œํ›„ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋„์šฐ๊ธฐ
    for (let i = 0; i < imageLists.length; i++) {
      const currentImageUrl = URL.createObjectURL(imageLists[i]);
      imageUrlLists.push(currentImageUrl);
    }
    //...
    setImgURLs(imageUrlLists);
    
    //...
    
    // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ
    <ImgPreview src={imgURL} />

๊ฐœ์„  ์ดํ›„

  // ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๋„์šฐ๊ธฐ
      //....
      let imgurls = [];
      for (let i = 0; i < state.item.imgs.length; i++) {
         imgurls.push(state.item.imgs[i].data); // base64 ๋ฐ์ดํ„ฐ
      }
      setImgURLs(imgurls);

    //....
    
    // ๋กœ์ปฌ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ
     const reader = new FileReader();
     reader.onload = () => {
       const base64Data = reader.result;
       setImgURLs((prevImgs) => [...prevImgs, base64Data.split(",")[1]]); // ์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ €์žฅ(base64ํ˜•์‹)
     };
      
  //...
  // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ด๋ฏธ์ง€ ์ปดํฌ๋„ŒํŠธ
  <ImgPreview src={`data:image/jpeg;base64, ${imgURL}`} />
 

๐Ÿ“‹ ๊ฒŒ์‹œ๋ฌผ(post) ์ˆ˜์ • - 2. ์ด๋ฏธ์ง€ ์‚ญ์ œ

  • ์–ด๋–ค ๊ธฐ์กด ์ด๋ฏธ์ง€๊ฐ€ ์‚ญ์ œ๋˜์—ˆ๊ณ  ์œ ์ง€๋˜๋ฉฐ, ๋กœ์ปฌ์—์„œ ์–ด๋–ค ์ด๋ฏธ์ง€๊ฐ€ ์ถ”๊ฐ€&์‚ญ์ œ๋˜๋Š”์ง€์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌํ•ด์•ผ ํ–ˆ๋‹ค.
  • ์ฒ˜์Œ์—๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ์ง€์šด ๋‹ค์Œ ์—…๋ฐ์ดํŠธ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ insert ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์„ ํƒํ–ˆ๋‹ค๊ฐ€, ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ(base64ํ˜•์‹)์„ ํŒŒ์ผ ํ˜•ํƒœ๋กœ ์ „ํ™˜ํ•˜๋Š”๋ฐ ์–ด๋ ค์›€์„ ๊ฒช์—ˆ๋‹ค.
  • ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์œผ๋กœ, ์‚ญ์ œํ•  ๋ฐ์ดํ„ฐ์˜ ํŒŒ์ผ๋ช…๊ณผ ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•œ ํŒŒ์ผ๋งŒ ์„œ๋ฒ„์— ์ „์†กํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ํƒํ–ˆ๋‹ค
  const [imgURLs, setImgURLs] = useState([]); /**์ด๋ฏธ์ง€ ๋ฏธ๋ฆฌ๋ณด๊ธฐ */
  const [imgFileName, setImgFileName] = useState([]); /** ๊ธฐ์กด ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช… (์ˆ˜์ •) */
  const [deleteFileList, setDeleteFileList] = useState([]); /** ์‚ญ์ œ๋  ํŒŒ์ผ๋ช… */
  
  useEffect(() => {
    if (state) {
       //.......
       let imgurls = [];
       let imgfilesname = [];
       for (let i = 0; i < state.item.imgs.length; i++) {
          imgurls.push(state.item.imgs[i].data); //base64 ๋ฐ์ดํ„ฐ - ๋ฏธ๋ฆฌ๋ณด๊ธฐ ๊ด€๋ จ
          imgfilesname.push(state.item.imgs[i].filename);  // ๊ธฐ์กด ์ด๋ฏธ์ง€ ํŒŒ์ผ๋ช…
       }
       setImgURLs(imgurls);
       setImgFileName(imgfilesname);
    }
  }, [state]);
 
 //...
   // ์ด๋ฏธ์ง€ ์‚ญ์ œ์‹œ ์‹คํ–‰ ํ•จ์ˆ˜
  const handleDelete = (index) => {
    setImgURLs(imgURLs.filter((_, idx) => idx !== index)); // ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ฒ˜๋ฆฌ
    if (index < imgFileName.length) { // ๊ธฐ์กด ์ด๋ฏธ์ง€ ๋ฐ์ดํ„ฐ ์‚ญ์ œ 
      setDeleteFileList((prev) => [...prev, imgFileName[index]]);
      setImgFileName(imgFileName.filter((_, idx) => idx !== index));
    } else { // ๋กœ์ปฌ์—์„œ ์ถ”๊ฐ€๋œ ์ด๋ฏธ์ง€ํŒŒ์ผ ์‚ญ์ œ
      setImgFile(
        imgFile.filter((_, idx) => idx + imgFileName.length !== index)
      );
    }
  };

๐Ÿ“‹ ํŒŒ์ผ๋ช… ์ค‘๋ณต ์ €์žฅ ๋ฒ„๊ทธ

  • ๊ฒŒ์‹œ๋ฌผ ์ž‘์„ฑ์‹œ ์—…๋กœ๋“œ ๋˜๋Š” ์ด๋ฏธ์ง€๋Š” node.js์˜ multer ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ์ €์žฅ
  • ์—…๋กœ๋“œ ์‹œ๊ฐ„(Date.now())์œผ๋กœ ํŒŒ์ผ ์ด๋ฆ„์„ ๊ตฌ๋ถ„ํ•˜๋ ค ํ•˜์˜€์œผ๋‚˜ ํฌ๊ธฐ๊ฐ€ ์ž‘์€ ํŒŒ์ผ(ex:.png)์˜ ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ์†๋„๊ฐ€ ๋นจ๋ผ ๊ฐ™์€ ์‹œ๊ฐ„์— ์ฒ˜๋ฆฌ๋˜์–ด ๋™์ผํ•œ ํŒŒ์ผ์ด๋ฆ„์œผ๋กœ ์ €์žฅ๋˜๋Š” ๋ฌธ์ œ ๋ฐœ์ƒ

๊ฐœ์„  ์ „
  filename: function (req, file, cb) {
    const extension = path.extname(file.originalname);
    const filename = `${Date.now()}${extension}`;
    cb(null, filename);
  },

๊ฐœ์„  ์ดํ›„

  • ์—…๋กœ๋“œ ์‹œ๊ฐ„๊ณผ ๊ธฐ์กด ํŒŒ์ผ๋ช…์„ ํ˜ผํ•ฉํ•˜์—ฌ ํŒŒ์ผ๋ช…์„ ์ˆ˜์ •ํ•จ
  • ๊ธฐ์กด ํŒŒ์ผ๋ช…์ด ํ•œ๊ธ€์ผ ๊ฒฝ์šฐ ๋ฌธ์ž๊ฐ€ ๊นจ์ง€๋Š” ๊ฒฝ์šฐ๋ฅผ multer๋ฅผ 1.4.4 ๋ฒ„์ „์œผ๋กœ ๋‹ค์šด๊ทธ๋ ˆ์ด๋“œํ•˜์—ฌ ํ•ด๊ฒฐ
  filename: function (req, file, cb) {
    const extension = path.extname(file.originalname);
    const name = file.originalname.split(".")[0];
    const filename = `${Date.now()}${name}${extension}`;
    cb(null, filename);
  },

๐Ÿ“‹ Modal ๊ด€๋ จ ๋ฒ„๊ทธ ํ•ด๊ฒฐ

  useEffect(() => {
    const body = document.querySelector("body");
    if (imgFullModal || activeGrade) {
      body.classList.add("no-scroll");
    } else if (!imgFullModal && !activeGrade) {
      body.classList.remove("no-scroll");
    }
    return ()=>body.classList.remove("no-scroll");
  }, [imgFullModal, activeGrade]);
  • ๋ชจ๋‹ฌ ์‚ฌ์šฉ ์‹œ ์Šคํฌ๋กค ๋ฐฉ์ง€

  • ์–ธ๋งˆ์šดํŠธ์‹œ(return) ์Šคํฌ๋กค ๋ฐฉ์ง€๋ฅผ ์ œ๊ฑฐํ•ด ์ฃผ์ง€ ์•Š์„ ๊ฒฝ์šฐ, ๋ชจ๋‹ฌ active ์ƒํƒœ์—์„œ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ ์‹œ ์—ฌ์ „ํžˆ ์Šคํฌ๋กค์ด ๋ง‰ํ˜€์žˆ๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒ


    ๐Ÿ“‹ React-Swiper currentIdx ๋ฒ„๊ทธ ํ•ด๊ฒฐ

  // swiper onSlideChange ์‹œ - ํ˜„์žฌ ์ด๋ฏธ์ง€์˜ ์ธ๋ฑ์Šค ์ €์žฅ ํ•จ์ˆ˜
  const handleSlideChange = (swiper) => {
    setImgCurrentIdx(swiper.realIndex);
  };
  
  //...
  
   <StyledSwiper
      //...
      loop={true}
      onSlideChange={handleSlideChange}
   >
   </StyledSwiper>
  • ๊ธฐ์กด์— ์‚ฌ์šฉํ–ˆ๋˜ swiper.activeIndex๋Š” Swiper ์ปดํฌ๋„ŒํŠธ๊ฐ€ loop ๋ชจ๋“œ์ผ ๊ฒฝ์šฐ์— ์ •ํ™•ํ•œ ์ธ๋ฑ์Šค๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€ ๋ชปํ•จ
  • swiper.realIndex๋กœ ๋Œ€์ฒด

๐Ÿ“‹ useMutation - ์ฐœ(์•„์ด์ฝ˜) ์ƒํƒœ ๋ณ€๊ฒฝ

	const queryClient = useQueryClient();
  const { mutate: mutateHeart } = useMutation(
    (heart) => heartChangeApi(heart),
    {
	// ์„œ๋ฒ„ ์š”์ฒญ ์™„๋ฃŒ ํ›„ ์—…๋ฐ์ดํŠธ ์™„๋ฃŒ๋œ ์ตœ์‹  ์ •๋ณด๋ฅผ ํ™”๋ฉด์— ๊ทธ๋ฆฌ๋Š” ๊ฒฝ์šฐ
	// onSuccess: () => {
      //   queryClient.invalidateQueries(["postDatail", postId);
      // },

	// ์˜ตํ‹ฐ๋ฏธ์Šคํ‹ฑ ์—…๋ฐ์ดํŠธ
	onMutate: async (newData) => {
        const previousHeartData = queryClient.getQueryData([
          "postDatail",
          postId,
        ]);
        queryClient.setQueryData(["postDatail", postId], (olddata) => {
          return { ...olddata, heart: !newData.heart };
        });
        return previousHeartData; //์š”์ฒญ ์‹คํŒจํ•  ๊ฒฝ์šฐ ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ
      },
	//์š”์ฒญ์ด ์‹คํŒจํ•  ๊ฒฝ์šฐ ์ด์ „์ƒํƒœ ์œ ์ง€
      onError: (rollback) => rollback(),

    }
  );
  • mutation ์„ฑ๊ณต ํ›„ ์ฟผ๋ฆฌ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•˜๋Š” ๋ฐฉ๋ฒ•์—์„œ ์˜ตํ‹ฐ๋ฏธ์Šคํ‹ฑ ์—…๋ฐ์ดํŠธ๋กœ ๋ณ€๊ฒฝํ•จ์— ๋”ฐ๋ผ ๋น ๋ฅธ ๋ฐ˜์‘์†๋„

5. ํŒŒ์ผ ๊ตฌ์กฐ