Front-End - Main Menu/React (Next.js)

[React.js] useEffect로 불필요하게 두 번 이상 실행되어 API가 중복 호출되는 문제 해결하기 (feat. MyApp 프로젝트)

ITRecipe 2025. 3. 14. 11:28

안녕하세요 itrecipe 입니다.
현생이 바빠 오랜만에 포스팅 합니다.

토이 프로젝트를 진행하면서 마주한 문제에 대한
어려움과 해결법을 간략히 남기고자 합니다.

그럼 시작하겠습니다.

[ListContainer.jsx] : 기존 문제 발생 코드

import BoardList from "../../components/board/BoardListForm";
import * as boards from '../../apis/boards'
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";

const ListContainer = () => {

  // TODO: boardlist state 선언
  // state 선언
  const [boardList, setBoardList] = useState([])
  const [pagination, setPagination] = useState({}) // useState({}) 여기서는 객체로 넘어오기 떄문에 빈객체로 넣어둠
  const [page, setPage] = useState(1)
  const [size, setSize] = useState(10)

  /* 페이지 네이션 초기 코드
    ?파라미터 = 값 가져오는 법
    const location = useLocation()
    const query = new URLSearchParams(location.search)
    const page = query.get("page") ?? 1 //"page" 파라미터 값을 가져온다. 기본값이 없을 경우 ?? 1 or ?? 10 이런식으로 값을 임의로 세팅하기
    const size = query.get("size") ?? 10
  */

 // ?파라미터 = 값 가져오는 법
 const location = useLocation() // 전역에서 사용할 수 있도록 분리
 
 const updatePage = () => {
    const query = new URLSearchParams(location.search)
    const newPage = query.get("page") ?? 1
    const newSize = query.get("size") ?? 10
    
    console.log(`updatePage() -> newPage : ${newPage}`)
    console.log(`updatePage() -> newSize : ${newSize}`)
    
    setPage(newPage)
    setSize(newSize)
    // getList()
  }

  // 게시글 목록 데이터 요청 (기존 게시판 코드)
/*   const getList = async () => {
    const response = await boards.list()
    const data = await response.data
    const list = data.list
    const pagination = data.pagination

    // 디버깅 코드 - 2
    console.dir('getList() -> data 확인 : ', data)
    console.dir('getList() -> data.list 확인 : ', data.list)
    console.dir('getList() -> data.pagination 확인 : ', data.pagination)

    // 디버깅 코드 - 1
    // console.log(`data : ${data}`)
    // console.log(`list : ${list}`)
    // console.log(`pagination : ${pagination}`)

    // TODO: boardList state 업데이트

    setBoardList( list )
    setPagination( pagination )
  } 
  */

  // 게시글 목록 데이터 요청 (페이지네이션 기능 확장)
  const getList = async () => {
    const response = await boards.list(page, size)
    const data = await response.data
    const list = data.list
    const pagination = data.pagination

    // 디버깅 코드 - 2
    console.dir('getList() -> data 확인 : ', data)
    console.dir('getList() -> data.list 확인 : ', data.list)
    console.dir('getList() -> data.pagination 확인 : ', data.pagination)

    // 디버깅 코드 - 1
    // console.log(`data : ${data}`)
    // console.log(`list : ${list}`)
    // console.log(`pagination : ${pagination}`)

    // TODO: boardList state 업데이트

    setBoardList( list )
    setPagination( pagination )
  }

  useEffect( () => {
    updatePage()
  }, [location.search])
  
  useEffect( () => {
    getList()
  }, [page, size])


  return (
    // <></> 리액트 프래그먼트 문법이자 축약형 문법 : 여러 요소를 하나의 부모로 묶을 수 있도록 해줌 (그룹화)
    <>
      <div>ListContainer</div>
      <BoardList boardList={boardList} pagination={pagination} />
    </>
  );
};

export default ListContainer;

[ListContainer.jsx] : 개선된 코드

import BoardList from "../../components/board/BoardListForm";
import * as boards from '../../apis/boards';
import { useEffect, useState } from "react";
import { useLocation } from "react-router-dom";

const ListContainer = () => {
  const [boardList, setBoardList] = useState([]);
  const [pagination, setPagination] = useState({});
  const location = useLocation();

  useEffect(() => {
    const query = new URLSearchParams(location.search);
    const newPage = Number(query.get("page")) || 1;
    const newSize = Number(query.get("size")) || 10;
    
    console.log(`updatePage() -> newPage: ${newPage}`);
    console.log(`updatePage() -> newSize: ${newSize}`);
    
    const getList = async (pageParam, sizeParam) => {
      const response = await boards.list(pageParam, sizeParam);
      const data = response.data;
      console.dir('getList() -> data 확인 : ', data);
      setBoardList(data.list);
      setPagination(data.pagination);
    };
    
    getList(newPage, newSize);
  }, [location.search]);

  return (
    <>
      <div>ListContainer</div>
      <BoardList boardList={boardList} pagination={pagination} />
    </>
  );
};

export default ListContainer;

[트러블 슈팅]

  • 문제 상황 : useEffect가 불필요하게 두 번 실행되어 API가 중복 호출됨.

  • 원인 분석 :
    • React의 StrictMode가 개발 환경에서 useEffect를 두 번 실행함.
    • page와 size를 useState로 따로 관리하면서, useEffect가 각각 실행됨.
    • updatePage()와 getList()가 따로 호출되면서 API가 중복 요청됨.
  • 해결 방법 :
    • 기존에는 page와 size를 useState로 관리하여 변경될 때마다 useEffect가 실행됨.
    • 개선된 코드: location.search에서 즉시 page와 size 값을 추출하여 API를 한 번만 호출하도록 변경.
    • useEffect 내부에서 getList()를 호출하여 한 번만 실행되도록 최적화.
    • main 컴포넌트에서 사용중인 StrictMode를 끄거나 주석 처리를 하면 중복 호출 방지됨.
  • 결과 :
    •  useEffect 중복 실행이 방지됨.
    •  API가 한 번만 호출되면서 불필요한 요청 제거.
    • 성능 최적화 및 코드 가독성 향상.



StrictMode 제거 전
StrictMode 제거 후