문제
심심해서 js 디버깅을 돌려보던 중, 갑자기 Caught Execption이 나를 반겨줬다.

콜 스택을 따라 거슬러 올라가보니 해당 부분에서 원인을 찾을 수 있었다.
export const checkDynamicCreation = (displayName: string, componentId?: string | undefined) => { if (process.env.NODE_ENV !== 'production') { const parsedIdString = componentId ? ` with the id of "${componentId}"` : ''; const message = `The component ${displayName}${parsedIdString} has been created dynamically.\n` + "You may see this warning because you've called styled inside another component.\n" + 'To resolve this only create new StyledComponents outside of any render method and function component.'; // If a hook is called outside of a component: // React 17 and earlier throw an error // React 18 and above use console.error const originalConsoleError = console.error; try { let didNotCallInvalidHook = true; console.error = (consoleErrorMessage, ...consoleErrorArgs) => { // The error here is expected, since we're expecting anything that uses `checkDynamicCreation` to // be called outside of a React component. if (invalidHookCallRe.test(consoleErrorMessage)) { didNotCallInvalidHook = false; // This shouldn't happen, but resets `warningSeen` if we had this error happen intermittently seen.delete(message); } else { originalConsoleError(consoleErrorMessage, ...consoleErrorArgs); } }; // We purposefully call `useRef` outside of a component and expect it to throw // If it doesn't, then we're inside another component. useRef(); if (didNotCallInvalidHook && !seen.has(message)) { console.warn(message); seen.add(message); } } catch (error) { // The error here is expected, since we're expecting anything that uses `checkDynamicCreation` to // be called outside of a React component. if (invalidHookCallRe.test((error as Error).message)) { // This shouldn't happen, but resets `warningSeen` if we had this error happen intermittently seen.delete(message); } } finally { console.error = originalConsoleError; } } };
이 코드는 React의 훅(
useRef
)을 컴포넌트가 아닌 곳에서 호출했을 때 발생하는 오류나 경고를 확인해서, styled-components가 “동적으로 컴포넌트를 생성”하고 있음을 감지하고 경고를 띄우기 위한 목적을 가지고 있다.차근차근히 살펴보자.
React 훅(
useRef
)은 반드시 컴포넌트 함수(혹은 커스텀 훅) 내부에서만 호출해야 한다. 만약 바깥에서 호출하면 React가 “Invalid hook call” 에러(React 17 이하) 혹은 console.error
로그(React 18 이상)를 발생시킨다. 이 부분을 활용해 “지금 이 코드가 혹시 컴포넌트 내부에서 실행되고 있지 않은지”를 판별하려고 한다.이를 확인하기 위해 useRef를 호출한다.
... useRef(); ...
에러 발생 → 정상적으로 컴포넌트 바깥에서 선언됐으므로 정상
에러 미발생 → 컴포넌트 내부에 선언됐다는 의미이므로 비정상
따라서 비정상인 경우에는 경고를 띄워줘야 한다. React 17이하에서는 useRef를 잘못 실행시킨 경우에 실제 에러를 throw 하고, 18이후에서는 console.error를 띄운다. 그래서 다음과 같이 console.error 함수를 오버라이딩해서 다음과 같은 정규식에 해당하는 에러가 출력되는지 확인한다.
const invalidHookCallRe = /invalid hook call/i; ... let didNotCallInvalidHook = true; console.error = (consoleErrorMessage, ...consoleErrorArgs) => { if (invalidHookCallRe.test(consoleErrorMessage)) { didNotCallInvalidHook = false; // ... } else { originalConsoleError(consoleErrorMessage, ...consoleErrorArgs); } };
출력되면 정상이므로 didNotCallInvalidHook을 false로 만들게 된다.
만약 출력되지 않으면 didNotCallInvalidHook은 true를 유지하게 되므로 다음 if문을 통과하게 된다.
if (didNotCallInvalidHook && !seen.has(message)) { console.warn(message); seen.add(message); }
seen은 해당 오류 메시지가 딱 1번만 출력되도록 이전에 출력된 메시지들을 관리하는 자료구조 Set이다.
해당 메시지가 한번도 출력되지 않았다면 경고문을 출력하도록 한다. 따라서 컴포넌트 내부에 styled.div를 선언하면 다음과 같은 경고문을 확인할 수 있을 것이다.

그럼 왜 styled component를 컴포넌트 내부에 선언하면 안되나? - GPT
React 컴포넌트의 렌더링 과정에서 매번 새로운 styled 컴포넌트를 생성하면 다음과 같은 문제가 발생할 수 있습니다:
- 매 렌더마다 새로운 컴포넌트 정의
- 컴포넌트(예:
styled.div
)는 자바스크립트 객체로서, 정의 위치에 따라 매번 새로운 레퍼런스가 만들어집니다. - React는 컴포넌트를 비교(리콘실리에이션)할 때 참조가 바뀌었는지 여부 등을 사용하므로, 매 렌더마다 새로운 컴포넌트 레퍼런스가 생성되면 불필요한 리마운트나 재렌더가 일어날 수 있습니다.
- 성능 저하
- styled-components의 구현상, 새 컴포넌트를 정의할 때 마다 내부적으로 유니크한 클래스 이름을 생성하고, 이를 CSS와 연결하는 로직을 수행합니다.
- 렌더 때마다 새 스타일 정의가 이루어지면, 불필요하게 CSS가 다시 계산되거나, 새로운 스타일 규칙이 중복으로 삽입될 수 있습니다.
- 이러한 과정은 트리거되는 빈도가 높아질수록 앱 성능에 악영향을 미칩니다.
- React 훅 규칙 위반 위험
- 일반적으로 styled-component 정의 시점에는 훅(
useTheme
등)을 사용하지 않아야 하지만, 만약 내부 로직에서 훅이 사용되고 있다면 “렌더 메서드 안에서 훅 정의” 같은 패턴이 무의식적으로 추가될 수 있습니다. - 이는 훅 규칙(“항상 컴포넌트의 최상위 레벨에서 훅을 호출”)을 어기게 되어, 예상치 못한 에러가 발생할 수 있습니다.
- 코드 유지보수 및 가독성 저하
- 스타일 정의는 대개 컴포넌트 외부에 위치시켜야, 스타일과 로직이 분리되고 코드 흐름도 명확해집니다.
- 컴포넌트 내부에 styled를 정의해두면 “렌더 시점에 스타일 정의가 실행되는” 형태가 되어, 추적과 유지보수가 복잡해질 수 있습니다.
결론
- 성능: 렌더마다 새 컴포넌트가 생성되어 불필요한 재렌더 및 CSS 업데이트를 야기할 수 있음
- 리액트 원칙 준수: 훅이나 스타일 정의를 반복적으로 생성하지 않도록, 반드시 컴포넌트 밖 또는 최상위 레벨에서 선언해야 함
- 가독성 및 유지보수: 스타일 정의는 한 번만 선언하고 재사용하는 것이 바람직함
따라서 styled를 다른 함수 컴포넌트나 렌더 메서드 내부에서 새로 생성하면 안 되며, 컴포넌트 외부 최상위 스코프에서 한 번만 선언하여 사용하는 것이 가장 올바른 사용법입니다.