최근 개발 환경에서 아래와 같은 에러를 마주했습니다.
No QueryClient set, useQueryClientProvider to set one
에러 정보를 기반으로 추론했을 때 useSuspenseQuery를 호출하려 하는데 루트에 QueryProvider가 없어서 발생하는 에러인 것 같습니다. 그런데 이상합니다. 분명 루트에는 QueryProvider가 감싸져 있습니다. 그런데도 계속 에러가 발생하는 것을 보고 packages 관련 문제라는 것을 직감했습니다. 빠르게 방향을 잡을 수 있었던 이유는 과거에 모노레포 환경에서 node_modules packages hoisting 문제를 겪고 해결한 경험이 있었기 때문입니다. 그때는 yarn v1을 패키지 매니저로 사용했었고 현재는 pnpm을 사용하지만 같은 맥락의 에러라고 판단했습니다. (엄밀히 말하면 위 에러는 node_modules hoisting이 원인은 아니었습니다.)
가장 먼저 node_modules/.pnpm에서 설치된 @tanstack/react-query를 확인해 봤습니다. 2개의 @tanstack/react-query가 설치되어 있는 것을 확인했습니다. 위의 @tanstack/react-query는 react-dom@18.2.0, react@18.2.0 두 개의 패키지가 peer dependencies이며 아래의 @tanstack/react-query는 두 개의 패키지에 더해 react-native@0.73.6을 추가로 peer dependencies로 가집니다. (How peers are resolved)
node_modules
.pnpm
@tanstack+react-query@4.36.1_react-dom@18.2.0_react@18.2.0
@tanstack+react-query@4.36.1_react-dom@18.2.0_react-native@0.73.6_react@18.2.0
여기까지 얻은 정보를 바탕으로 에러의 원인을 추론할 수 있습니다. useSuspenseQuery를 import 할 때 사용한 @tanstack/react-query와 루트에서 QueryProvider를 import 할 때의 @tanstack/react-query가 서로 다르기 때문에 useSuspenseQuery에서 QueryProvider의 prop으로 넘긴 QueryClient를 찾을 수 없어서 에러가 발생한 것입니다. 다이어그램으로 살펴보죠.
그럼 위의 추론이 맞는지 pnpm-lock.yml에서 확인해보겠습니다. useSuspenseQuery를 가지는 @suspensvie/react-query에서 react-dom@18.2.0, react@18.2.0, react-native@0.73.6을 peer dependencies로 가지는 @tanstack/react-query를 사용하고 있습니다.
/@suspensive/react-query@1.18.3(@suspensive/react@1.18.3)(@tanstack/react-query@4.36.1)(react@18.2.0):
peerDependencies:
'@suspensive/react': ^1.18.3
'@tanstack/react-query': ^4
react: ^16.8 || ^17 || ^18
dependencies:
'@suspensive/react': 1.18.3(react@18.2.0)
'@tanstack/react-query': 4.36.1(react-dom@18.2.0)(react@18.2.0)(react-native@0.73.6) // peer dependencies
react: 18.2.0
루트에서 사용한 @tanstack/react-query는 react-native@0.73.6을 peer dependency로 가지지 않기 때문에 위에서 추론한 원인이 사실임을 확인했습니다.
'@tanstack/react-query':
specifier: ^4.32.6
version: 4.36.1(react-dom@18.2.0)(react@18.2.0)
해결하는 과정은 간단했습니다. 아무 패키지나 삭제하고 재설치하면 pnpm이 peer dependencies를 재조정하기 때문에 같은 peer dependencies를 가지도록 수정됐습니다.
그런데 뭔가 이상합니다. peer dependencies로 react, react-dom은 이해하겠는데 react-native는 뭘까요? 모바일이 아닌 웹에서 개발하기 때문에 react-native를 peer dependencies로 가질 이유가 없어보입니다. 원인을 찾기 위해 @tanstack/react-query package.json에 peer dependency field를 확인해봤습니다.
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0",
"react-native": "*"
},
"peerDependenciesMeta": {
"react-dom": {
"optional": true
},
"react-native": {
"optional": true
}
}
react-native는 optional이라 문제 될 건 없습니다. 자동으로 peer dependency를 설치했더라도 optional peer dependency는 제외하고 설치되기 때문입니다.
여기서 react-native를 peer dependency로 가지는 다른 패키지가 있다는 것을 추론할 수 있습니다. 다시 한번 pnpm-lock.yml 파일을 확인하여 어디서 react-native를 peer dependency로 가지는지 보겠습니다.
2024.4.14 업데이트
manual하게 찾는 것 대신에 아래 cli를 활용해서 react-native를 dependency로 가지는 패키지를 쉽게 찾을 수 있습니다.
$ pnpm list -r --depth Infinity | grep "react-native"
찾았습니다! @react-spring/native 패키지에서 react-native를 peer dependency로 가지고 있더군요. 이 녀석 때문에 react-native를 optional로 가지는 다른 패키지에 영향을 주고 있던 것이었습니다. (앞서 @tanstack/react-query에 react-native가 peer dependency로 명시됐던 것)
/@react-spring/native@9.7.3(react-native@0.73.6)(react@18.2.0):
peerDependencies:
react: ^16.8.0 || >=17.0.0 || >=18.0.0
react-native: '>=0.58'
dependencies:
// ...
react-native: 0.73.6(@babel/core@7.24.4)(@babel/preset-env@7.24.4)(react@18.2.0)
react-spring 문서를 확인해 보면 react-spring을 설치하는 경우 모든 종류의 패키지를 다운로드하기 때문에 그 안에 react-native에 대한 패키지도 같이 설치가 됩니다. 따라서 아래처럼 웹에 대한 패키지만 설치하는 것을 권장하고 있습니다.
# Install the entire library
pnpm install react-spring
# or just install your specific target (recommended)
pnpm install @react-spring/web
react-spring을 제거하고 @react-spring/web을 설치하여 react-native와 관련된 모든 패키지가 제거됐습니다!
마치며
발생하는 문제는 여러 계층의 원인이 있습니다. 이를 타고 올라가 보면 근본적인 원인을 발견하게 되고 이를 해결하는 것이 바로 문제를 해결했다라고 말할 수 있을 것입니다. 이번에 공유드린 경험을 빗대어 보면 다음과 같습니다.
문제: @tanstack/react-query 에러가 발생했다.
원인: peer dependency(react-native)가 다른 @tanstack/react-query가 2개가 존재한다.
문제: 왜 react-native를 peer dependency로 가질까?
원인: react-spring을 설치할 때 @react-spring/native가 같이 설치되는데 이 패키지에서 react-native를 peer dependency로 가지기 때문이다.
해결: react-spring을 제거하고 @react-spring/web을 설치해 @react-spring/native가 같이 설치되지 않도록 한다.
사실 @tanstack/react-query를 원인으로 해결하여 끝내려 했는데 몇 주가 지난 지금 찝찝함에 다시 디버깅해 보니 근본적인 원인을 파악할 수 있었습니다. 근본적인 원인을 찾아 해결하려고 하지 않았던 것을 반성하고 이번 경험을 통해 문제해결력을 성장시킬 수 있어서 제겐 값진 경험이었던 것 같습니다.
'Frontend' 카테고리의 다른 글
defer, async 스크립트 더 들여다보기 (0) | 2024.03.25 |
---|---|
브라우저 Memory leak 디버깅 해보기 (0) | 2024.03.10 |
V8 엔진에서 number, string은 어떻게 처리될까? (0) | 2024.03.09 |
HTTP Cache 제대로 알기 (0) | 2023.11.30 |
웹 성능 세션을 준비하여 공유해보자 (4) | 2023.11.26 |