profile image

L o a d i n g . . .

해결하기 전 코드

// 지점 데이터 페이징 처리
    const pageingBranchs = async () => {
        try {
            const token = localStorage.getItem('accessToken');
            // 지점 데이터 가져오는 함수 호출 (토큰 필요)
            await getBranchs(token);
        } catch (error) {
            // 403 == 토큰 만료
            if (error.response && error.response.status === 403) {
                try {
                    const newToken = await refreshAccessToken();
                    await getBranchs(newToken);
                } catch (error) {
                    // 새 토큰 요청도 실패하면 인증 만료 알림 및 로그아웃 처리
                    alert("인증이 만료되었습니다. 다시 로그인 해주세요.");
                    handleAdminLogout();
                }
            } else {
                console.error('There was an error fetching the branchs pageing!', error);
            }
        }
    };

    // 지점 데이터 가져오기
    const getBranchs = async (token) => {
        const params = {
            pageSize, // 페이지 크기
            pageNumber, // 현재 페이지 번호
        };

        // 검색 조건이 있다면
        if (searchName && searchName.trim() !== '') {
            // 검색어(지점명)를 params에 추가
            params.branchName = searchName;
        }

        try {
            const response = await axios.get(${process.env.REACT_APP_API_URL}/arentcar/manager/branchs/paged, {
                params,
                headers: {
                    Authorization: Bearer ${token},
                },
                withCredentials: true,
            });

            if (response.data && response.data.length === 0) {
                setBranchs([]); 
                setPageNumber(1); 
                return;
            } else {
                setBranchs(response.data); // 응답이 있다면 지점 데이터 상태에 저장
                getTotalCount(); // 검색 결과에 맞는 총 개수 다시 가져오기
            }
        } catch (error) {
            console.error('Error fetching branches:', error);
        }
    };

    // 전체 지점 수 가져오기
    const getTotalCount = async () => {
        try {
            const token = localStorage.getItem('accessToken');
            // 총 개수를 가져오는 함수 호출, await를 이용하여 API 요청 끝날때까지 대기 후 코드 실행
            await getCount(token);
        } catch (error) {
            if (error.response && error.response.status === 403) {
                try {
                    const newToken = await refreshAccessToken();
                    await getCount(newToken);
                } catch (error) {
                    alert("인증이 만료되었습니다. 다시 로그인 해주세요.");
                    handleAdminLogout();
                }
            } else {
                console.error('There was an error fetching the branchs count!', error);
            }
        }
    };

    useEffect(() => {
        if (branchs.length === 0 && searchName.trim() !== '') {
            alert("존재하지 않는 지점명입니다. 다시 입력해주세요.");
        }
    }, [branchs]); // branchs 상태가 빈 배열로 변경될 때만 alert

 


해결 된 코드

    // 지점 데이터 페이징 처리
    const pageingBranchs = async () => {
        try {
            const token = localStorage.getItem('accessToken');
            // 지점 데이터 가져오는 함수 호출 (토큰 필요)
            await getBranchs(token);
        } catch (error) {
            // 403 == 토큰 만료
            if (error.response && error.response.status === 403) {
                try {
                    const newToken = await refreshAccessToken();
                    await getBranchs(newToken);
                } catch (error) {
                    // 새 토큰 요청도 실패하면 인증 만료 알림 및 로그아웃 처리
                    alert("인증이 만료되었습니다. 다시 로그인 해주세요.");
                    handleAdminLogout();
                }
            } else {
                console.error('There was an error fetching the branchs pageing!', error);
            }
        }
    };

    // 지점 데이터 가져오기
    const getBranchs = async (token) => {
        const params = {
            pageSize, // 페이지 크기
            pageNumber, // 현재 페이지 번호
        };

        // 검색 조건이 있다면
        if (searchName && searchName.trim() !== '') {
            // 검색어(지점명)를 params에 추가
            params.branchName = searchName;
        }

        try {
            const response = await axios.get(`${process.env.REACT_APP_API_URL}/arentcar/manager/branchs/paged`, {
                params,
                headers: {
                    Authorization: `Bearer ${token}`,
                },
                withCredentials: true,
            });

            if (response.data && response.data.length === 0) {
                // if (branchs.length !== 0) { 
                //     setBranchs([]); // 상태가 이미 빈 배열이면 업데이트하지 않음
                // }
                setPageNumber(1); 
                return;
            } else {
                setBranchs(response.data); // 응답이 있다면 지점 데이터 상태에 저장
                getTotalCount(); // 검색 결과에 맞는 총 개수 다시 가져오기
            }
        } catch (error) {
            console.error('Error fetching branches:', error);
        }
    };

    // 전체 지점 수 가져오기
    const getTotalCount = async () => {
        try {
            const token = localStorage.getItem('accessToken');
            // 총 개수를 가져오는 함수 호출, await를 이용하여 API 요청 끝날때까지 대기 후 코드 실행
            await getCount(token);
        } catch (error) {
            if (error.response && error.response.status === 403) {
                try {
                    const newToken = await refreshAccessToken();
                    await getCount(newToken);
                } catch (error) {
                    alert("인증이 만료되었습니다. 다시 로그인 해주세요.");
                    handleAdminLogout();
                }
            } else {
                console.error('There was an error fetching the branchs count!', error);
            }
        }
    };

    useEffect(() => {
        console.log("useEffect 실행: branchs.length =", branchs.length);
        // 상태가 빈 배열이 되고, 검색어가 있는 경우에만 alert 실행
        if (branchs.length === 0 && searchName.trim() !== '') {
            setTimeout(() => {
                alert("존재하지 않는 지점명입니다. 다시 입력해주세요.");
            }, 0); // 비동기적으로 alert 호출
        }
    }, [branchs]); // branchs 상태 변경 감지
    

    // 총 지점 수 요청
    const getCount = async (token) => {
        // 검색어(searchName)이 있다면 params에 추가
        const params = searchName ? { branchName: searchName } : {};

        try {
            // API 요청: 지점 수 가져오기
            const response = await axios.get(`${process.env.REACT_APP_API_URL}/arentcar/manager/branchs/count`,
                {
                    params,
                    headers: {
                        Authorization: `Bearer ${token}`
                    },
                    withCredentials: true,
                });

            if (typeof response.data === 'number') {
                setTotalCount(response.data);
            }
        } catch (error) {
            console.error('총 지점 수를 가져오는데 실패했습니다:', error);
        }
    };

    // 페이지 번호 / 크기가 바뀔 때 데이터 요청
    useEffect(() => {
        pageingBranchs(); // 지점 데이터 가져오기
        getTotalCount(); // 전체 지점 수 가져오기
    }, [pageNumber, pageSize]); // 페이지 번호, 크기가 변경될 때 실행

원인

1. React를 사용해 검색 기능을 구현하는 과정에서 검색어가 존재하지 않을 경우 alert를 띄우도록 코드를 작성했음

하지만 특정 조건에서 alert가 두 번 중복으로 호출되는 문제가 발생

  • `setBranchs([])`로 상태를 업데이트한 후 `useEffect`가 실행
if (response.data && response.data.length === 0) {
    setBranchs([]); 
    setPageNumber(1); 
    return;
}
useEffect(() => {
    if (branchs.length === 0 && searchName.trim() !== '') {
        alert("존재하지 않는 지점명입니다. 다시 입력해주세요.");
    }
}, [branchs]);
  • 이때, `branchs.length === 0` 조건이 만족되면 `alert`가 호출됨
  • 만약 `setBranchs([])`로 상태를 업데이트하기 전에 branchs가 이미 빈 배열이라면, 상태는 변하지 않았지만 `useEffect`가 여전히 실행될 가능성이 있음
  • 즉, API 응답에서 상태 변경이 일어나지 않아도 useEffect가 재실행 됨

해결 방법

1. useEffect에서 alert 중복 호출 방지

  • alert를 비동기 처리로 변경하여 상태 업데이트가 완료된 후 한 번만 호출되도록 제어.
  • 브라우저의 비동기 함수인 `setTimeout`을 사용하여 `alert` 호출을 비동기로 지연.
useEffect(() => {
    if (branchs.length === 0 && searchName.trim() !== '') {
        setTimeout(() => {
            alert("존재하지 않는 지점명입니다. 다시 입력해주세요.");
        }, 0); // setTimeout을 사용해 alert 호출을 이벤트 큐에 넣어서 비동기로 처리
    }
}, [branchs]);

 

 

2.중복 상태 업데이트 방지

  • `getBranchs` 함수 내부에서 상태를 변경하기 전에 현재 상태를 확인하고, 중복 업데이트 방지
if (response.data && response.data.length === 0) {
    if (branchs.length !== 0) { 
        setBranchs([]); // 상태가 이미 빈 배열이면 업데이트하지 않음
    } // 빈 배열이 아니라면 페이지 넘버를 1로 설정하고 반환
    setPageNumber(1); 
    return;
}

 

 

3. 디버깅으로 원인 확인

useEffect(() => {
    console.log("useEffect 실행: branchs.length =", branchs.length);
    if (branchs.length === 0 && searchName.trim() !== '') {
        setTimeout(() => {
            alert("존재하지 않는 지점명입니다. 다시 입력해주세요.");
        }, 0);
    }
}, [branchs]);

 


결과

  • alert가 정확히 한 번만 호출되도록 수정.
  • 상태 업데이트 중 중복 실행을 방지하여 불필요한 useEffect 호출을 차단함
복사했습니다!