
React로 Tooltip 컴포넌트를 구현하던 중 다음과 같은 ESLint 에러가 발생했다.
react-hooks/set-state-in-effect
Avoid calling setState() directly within an effect
코드를 보면 단순히 useEffect에서 setState를 호출하고 있을 뿐인데 왜 이런 에러가 발생했는지, 그리고 왜 기존의 mounted 패턴이 필요 없게 되었는지 정리해보았다.
Tooltip 컴포넌트는 createPortal을 사용해서 Tooltip UI를 document.body에 렌더링하고 있었다.
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
그리고 Tooltip 렌더링 시 다음과 같은 조건을 두고 있었다.
if (!mounted || !isVisible) return null;
하지만 ESLint에서는 다음과 같은 에러가 발생했다.
react-hooks/set-state-in-effect
Avoid calling setState() directly within an effect
해당 에러는 React 렌더링 구조와 Next.js의 SSR 구조와 관련된 문제였다.
위 코드는 흔히 mounted 패턴이라고 불린다.
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
if (!mounted) return null;
이 패턴의 목적은 컴포넌트가 브라우저에 마운트된 이후에만 UI를 렌더링하도록 만드는 것이다.
실제 렌더 흐름은 다음과 같다.
1. 첫 번째 render → mounted = false
2. useEffect 실행
3. setMounted(true)
4. 두 번째 render → mounted = true
render
↓
useEffect
↓
setState
↓
render
즉, 의도적으로 렌더를 한 번 더 발생시키는 패턴이다.
이 패턴은 주로 SSR(Server Side Rendering) 환경에서 사용되었다.
SSR에서는 다음과 같은 특징이 있다.
Server 환경
window ❌
document ❌
DOM ❌
즉 서버에서는 브라우저 객체가 존재하지 않는다.
따라서 이런 코드가 있으면 문제가 발생한다.
createPortal(..., document.body)
서버에서 document가 존재하지 않기 때문이다.
그래서 과거에는 다음과 같은 방식으로 해결했다.
Server render → 아무것도 렌더하지 않음
Client mount → 그 이후 렌더
즉 mounted 패턴을 통해 브라우저 환경에서만 렌더링하도록 만든 것이다.
use client 환경에서는?이번 Tooltip 컴포넌트에는 다음 코드가 이미 존재했다.
"use client";
Next.js(App Router)에서 "use client"는 해당 컴포넌트를 Client Component로 만든다.
Client Component의 특징은 다음과 같다.
브라우저에서 실행
window 접근 가능
document 접근 가능
DOM 접근 가능
즉 Tooltip 컴포넌트는 이미 브라우저 환경에서만 실행되는 컴포넌트였다.
따라서 다음과 같은 문제는 발생하지 않는다.
document.body 접근 에러
SSR 환경 문제
결국 mounted 패턴은 필요 없는 코드가 된다.
mounted 상태를 제거하고 필요한 경우에만 렌더하도록 수정한다.
기존 코드
if (!mounted || !isVisible) return null;
수정 코드
if (!isVisible) return null;
if (typeof document === "undefined") return null;
return createPortal(..., document.body);
이렇게 하면
세 가지를 모두 만족할 수 있다.
| 개념 | 설명 |
|---|---|
| mounted 패턴 | 브라우저 mount 이후 렌더하기 위한 패턴 |
| 사용 이유 | SSR 환경에서 document 접근 문제 방지 |
| 문제점 | 불필요한 추가 렌더 발생 |
Next.js "use client" | 브라우저 환경에서만 실행되는 컴포넌트 |
| 결론 | Client Component에서는 mounted 패턴이 대부분 필요 없음 |
mounted 패턴은 SSR 환경에서 브라우저 API 접근 문제를 해결하기 위한 패턴이지만,
Next.js의"use client"컴포넌트에서는 이미 브라우저에서 실행되므로 대부분 필요하지 않다.