이롭게 현명하게

[E-LOG] React Hook "useEffect" is called conditionally. 본문

T-LOG/E-LOG

[E-LOG] React Hook "useEffect" is called conditionally.

dev_y.h 2025. 10. 16. 18:10
728x90
반응형

오류

 


[목차]

오류 내용

원인

해결방법

 


[오류 내용]

<상황>

공공데이터포털에서 API를 사용해 데이터를 보여주는 프로젝트를 진행 중이었다.

API에서 제공하는 위치 데이터를 지도에 표시하는 과정에서 예외 처리를 시도하던 중 오류가 발생했다.

 

React Hook "useEffect" is called conditionally. 
React Hooks must be called in the exact same order in every component render. 
Did you accidentally call a React Hook after an early return?

[번역]

React Hook "useEffect"는 조건부로 호출됩니다. 
React Hook은 모든 구성 요소 렌더링에서 정확히 동일한 순서로 호출되어야 합니다. 
초기 복귀 후 실수로 React Hook을 호출하셨나요?

 


[원인]

<나의 코드>

import { useEffect } from "react";
import { Container as MapDiv, Marker, NaverMap, useNavermaps } from "react-naver-maps";
import "./index.css";
function MapBlock({ mapX, mapY }) {

    const navermaps = useNavermaps();
    
    if (!navermaps || !mapX || !mapY) {
        return <p>위치를 표시할 수 없습니다.</p>;
    }
    

    useEffect(() => {
        if (!navermaps || !mapX || !mapY) {
            return <p>위치를 표시할 수 없습니다.</p>;
        }
    }, [navermaps, mapX, mapY]);
    
    return (
        <>
            <MapDiv
                style={{
                    width: "100%",
                    height: "600px",
                }}
            >
                <NaverMap defaultCenter={new navermaps.LatLng(mapY, mapX)} defaultZoom={15}>
                    <Marker position={new navermaps.LatLng(mapY, mapX)} />
                </NaverMap>
            </MapDiv>
        </>
    );
}

export default MapBlock;

 

만약 mapX나 mapY의 값이 없어 지도에 표시를 못 한다면 <p> 위치를 표시할 수 없습니다. </p>를 반환했다.

그리고 그 아래에서 useEffect를 호출했다.

이때 문제가 되는 부분은 Hook 조건적으로 호출되었다는 것이다.

 

지금 보면 왜 이렇게 코드를 짠 건지 모르겠다.🥲
지도 좌푯값을 받아올 때 api에 없는 경우를 생각해서 if문으로 예외처리를 했다.
useEffect는... 왜... 저렇게 한 건지... ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ
아마도 '이렇게도 가능하지 않을까?'라는 생각으로 작성한 거 같다.

 

<문제 원인>

useEffect가 조건문보다 아래에 있어서 ReactHook 규칙을 위반하였다.

 if (!navermaps || !mapX || !mapY) {
        return <p>위치를 표시할 수 없습니다.</p>;
    }
useEffect(() => {
  ...
}, [navermaps, mapX, mapY]);

React에서 useEffect, useState 같은 hook은 항상 컴포넌트 함수의 최상단에서 호출되어야 한다.

렌더링마다 같은 순서로 호출되어야 한다.

내가 쓴 코드처럼 조건문에서 먼저 return을 해버리면 어떤 경우에는 useEffect가 호출되지 않는다.

 

<React Hook  규칙 >

  • Hook은 항상 컴포넌트 최상단에서 호출되어야 한다.
  • 조건문, 반복문, 함수 내에서 호출하면 안 된다.
  • 이유 : 리액트는 렌더링마다 hook 호출순서를 기준으로 상태가 효과를 추적한다.

 

<useEffect안에서 JSX를 반환하면 안 되는 이유>

useEffect(() => {
if (!navermaps || !mapX || !mapY) {
return <p>위치를 표시할 수 없습니다.</p>; // ❌ 잘못된 사용
}
}, [navermaps, mapX, mapY]);

useEffect는 렌더링 결과를 반환하는 함수가 아니다.

부수 효과(side-effect)를 처리하기 위한 용도이다.

콘솔로그를 찍거나 데이터 요청, 상태 변경 등의 작업을 할 때 사용한다.

JSX를 반환해야 할 경우 return을 통해 렌더링 영역에서 처리해야 한다.

 


[해결 방법]

<올바른 방법>

  • useEffect 내부는 JSX를 반환하면 안 된다.
    • jsx를 반환하려면 return문 바깥에서 처리해야 한다.
  • useEffect는 부수효과(side-Effect)를 수행하는 곳이다.
    • ex: console.log, API 호출, 상태 업데이트 등
  • Hook은 항상 컴포넌트 최상단에서 호출되어야 한다.
  • 조건문, 반복문, 함수 내부에서 Hook을 호출하면 안 된다.

지도 api에서 useEffect를 넣어야 하는 건가? 아님 그냥 단순 조건문만 넣어줘도 되는 건가? → if문으로 처리하는 게 가장 좋다.

<수정한 코드>

import { Container as MapDiv, Marker, NaverMap, useNavermaps } from "react-naver-maps";
import BoxShadowCard from "../../../component/Card/BoxShadowCard";
import FlexBox from "../../../component/Layout/FlexBox";
import Space from "../../../component/Layout/Space";
import Text from "../../../component/Text/Text";

function MapSection({ mapX, mapY }) {
	const navermaps = useNavermaps();
	if (!navermaps || !mapX || !mapY) {
	    return (
	        <FlexBox>
	            <Text>위치를 표시할 수 없습니다.</Text>
	        </FlexBox>
	    );
	}
	
	return (
	    <>
	        <Text fontWeight="bold" fontSize="24px">
	            캠핑장 위치
	        </Text>
	        <Space height={3} />
	        <BoxShadowCard>
	            <MapDiv
	                style={{
	                    width: "100%",
	                    height: "600px",
	                }}
	            >
	                <NaverMap defaultCenter={new navermaps.LatLng(mapY, mapX)} defaultZoom={15}>
	                    <Marker position={new navermaps.LatLng(mapY, mapX)} />
	                </NaverMap>
	            </MapDiv>
	        </BoxShadowCard>
	    </>
	);
}

export default MapSection;

 


잘못된 정보는 댓글에 남겨주시면 감사하겠습니다!☺️

댓글과 좋아요는 큰 힘이 됩니다!

728x90
반응형
Comments