문제점
로그인 인증을 구현하기 위해 아래와 같은 AuthGuard를 하나 만들고 Page Component에 감쌌습니다.
import { useRouter } from "next/navigation";
import { useAuth } from "./provider/AuthProvider";
const AuthGuard = ({ children }) => {
const auth = useAuth();
const router = useRouter();
if(!auth.authentication) {
router.push('/login');
return null;
}
return <>{children}</>;
};
그랬더니 아래와 같은 에러가 발생합니다.
React 컴포넌트의 렌더링 중에 상태 업데이트를 트리거하면 안 된다
React에서 "컴포넌트의 렌더링 중에 상태 업데이트를 트리거하면 안 된다"는 규칙은 React의 렌더링 메커니즘과 관련이 있습니다. 이 규칙을 어기면 React의 예상치 못한 동작이나 무한 루프 같은 성능 문제가 발생할 수 있습니다.
1. React의 단방향 데이터 흐름
React는 컴포넌트를 렌더링할 때 다음과 같은 데이터 흐름을 따릅니다.
- 상태변화
- 렌더링
- 화면 업데이트
상태 업데이트가 렌더링 중에 발생하면, 새로운 렌더링을 트리거합니다. 이로 인해 렌더링 중에 또 다른 렌더링을 호출하는 상황이 발생하고, 이는 무한 루프를 유발할 수 있습니다.
2. React의 스케줄링 최적화
React는 렌더링 과정을 최적화하기 위해 스케줄링 알고리즘을 사용합니다. 렌더링 중에 상태가 업데이트되면 React는 렌더링 과정을 중단하고 새로운 렌더링을 계획해야 하므로 최적화가 깨질 수 있습니다. 이는 React의 성능을 저하시킬 수 있습니다.
정상적인 흐름
- 상태가 변경됨(setState 호출)
- React가 렌더링 스케줄링 → 렌더 트리를 업데이트.
- 화면을 변경.
잘못된 흐름
- 상태가 변경됨(setState 호출)
- React가 렌더링 중인데 상태가 또 변경됨 (새로운 렌더링을 강제로 시작)
- React가 기존 렌더링을 취소하고 새 렌더링 스케줄링
3. 예측 가능한 렌더링 보장
React는 컴포넌트 렌더링이 순수 함수처럼 동작하기를 기대합니다. 즉, 동일한 상태와 props가 주어지면 항상 동일한 결과를 반환해야 합니다. 렌더링 중에 상태를 업데이트하면, 컴포넌트가 순수하지 않은 함수처럼 동작하여 렌더링 결과가 예측 불가능해질 수 있습니다.
const Component = () => {
const [text, setText] = useState("");
if (text === "") {
setText("Hello");
}
return <div>{text}</div>;
};
위 코드에서 렌더링 중에 상태가 업데이트되므로 React는 상태를 초기화하는 시점을 예측할 수 없습니다.
4. UI의 일관성 보장
렌더링 중에 상태 업데이트가 발생하면 React는 컴포넌트를 중간 상태로 렌더링할 가능성이 있습니다. 이는 UI가 순간적으로 불안정하거나 불완전한 상태에 놓일 수 있음을 의미합니다.
const Component = () => {
const [step, setStep] = useState(1);
if (step === 1) {
setStep(2); // 렌더링 중 상태 변경
}
return <div>{step}</div>;
};
React는 step 값이 1인 상태에서 렌더링을 시작하지만, 렌더링 도중 step이 2로 변경됩니다. 이로 인해 React는 예상치 못한 상태를 렌더링할 수 있습니다.
5. 코드 가독성과 유지보수성 저하
렌더링 중 상태를 업데이트하면 코드의 의도가 모호해지고 디버깅이 어려워질 수 있습니다. 컴포넌트의 렌더링은 상태와 props에 따라 결정되어야 하지만, 렌더링 중 상태를 변경하면 컴포넌트의 동작을 이해하거나 디버깅하는 데 어려움이 생깁니다.
해결방법
렌더링 중에 상태를 업데이트해야 할 경우, 이를 렌더링 후 단계(즉, useEffect 등)로 이동시켜야 합니다.
"use client"
import { useRouter } from "next/navigation"
import { useAuth } from "./provider/AuthProvider"
import { ReactNode, useEffect } from "react"
interface Props {
children: ReactNode
}
const AuthGuard = ({ children }: Props) => {
const auth = useAuth()
const router = useRouter()
useEffect(() => {
if(!auth.authentication) {
router.replace('/')
}
}, [auth, router])
if(!auth.authentication) {
return null
}
return <>{children}</>
}
export default AuthGuard
'개발일지' 카테고리의 다른 글
메일서버 구축 (postfix, dovecot) - 설치하기 (2) | 2024.11.20 |
---|