[React] Invalid hook call + Cannot read properties of null (reading ‘useState’) Issue: React 버전 미스 매칭 이슈

참고 사항

  • 회사에서 shell/sdk 라는 자체 개발 패키지를 사용 중(사내 플랫폼 연동용 SDK 라이브러리이며 문제의 원인)
  • 패키지 매니저: Yarn berry(v3.4.1)

문제

  • 회사에서 자체 개발하여 사용 중인 shell/sdk 패키지를 0.0.18 에서 0.0.21 로 버전 업데이트 후 앱을 실행하면 아래와 같은 오류가 발생undefined
undefined

원인

  • 외부 패키지인 shell/sdk와 개발 중인 프로젝트에서 서로 다른 버전의 react를 사용해서 발생한 문제
undefined

해결 방법

  • react 패키지를 어떤 버전으로 사용할 것인지 package.json 의 resolutions 필드에서 정의해준다.
    • resolutions 필드에 패키지 버전을 명시하면 프로젝트 내 모든 패키지가 특정 버전을 선택하도록 강제할 수 있다.
"resolutions": {
    "react": "^18.2.0",
    "react-dom": "^18.2.0"
  },

이후에 yarn install을 수행하면, 아래와 같이 shell/sdk 의 react 버전이 focus-fe 와 동일한 버전으로 설치된다. focus-fe@workspace로 표시된 것으로 알 수 있듯, 현재 팀에서 개발 중인 프로젝트 이름이 focus-fe 이다.

undefined

  • 혹은 yarn.lock 파일과 yarn cache clean 으로 기존 패키지를 제거하고, 새로 yarn install 한다.
    • 이 방식을 선택하면, focus-fe 의 react의 패키지 버전이 최신 버전으로 업데이트 된다.
undefined

  • 혹은 그냥 react 버전을 업데이트 해주면 된다.

심층 원인 파악: 두 방식의 차이는 무엇인가?

→ lock 파일의 존재 의의를 알아야 한다.

https://classic.yarnpkg.com/en/docs/yarn-lock/

yarn.lock 은 현재 설치된 패키지의 의존성과 패키지 버전을 명시한 파일이다. package-lock.json 도 마찬가지

문제가 발생하기 이전으로 돌아가 프로젝트의 yarn.lock에서 react 버전을 확인해보면 아래와 같다.

undefined

프로젝트에서 react는 버전 18.2.0으로 설치되어 있다.

이제 문제가 발생할 당시의 @shell/sdk 의존성을 확인해보았다**.**

shell/sdk 의 버전을 0.0.18 버전에서 0.0.21 버전으로 업데이트한 상황이다.

undefined

react@^18.3.1 을 의존성으로 명시되어 있다.

그리고 lock 파일에서 react를 확인해보면 아래와 같이 두 개 버전의 react가 설치되어 있음을 확인할 수 있다.

undefined

yarn cache 폴더를 확인해보니 이렇게 react@18.3.1 버전의 패키지가 설치되어 있었다.

undefined

yarn why react 로 react 패키지의 의존성 트리를 간략하게 확인해보았다. react의 의존성 버전이 서로 다르다.

undefined

이처럼 여러 버전의 react가 있을 경우, 특정 패키지가 현재 프로젝트와 다른 버전의 react를 참조할 때 충돌이 된다.

솔루션 분석

이제 yarn.lock 파일을 제거하고 재설치를 진행해보자. install 전에 yarn cache clean 도 진행한다.

확인해보면 설치된 react 버전이 18.3.1임을 확인할 수 있다.

undefined

undefined

작성일 기준 가장 최신 react 버전은 18.3.1 이므로, yarn 패키지 매니저가 18 메이저 버전 이내 가장 최신 패키지를 설치했음을 확인할 수 있다.

https://github.com/facebook/react/releases

undefined

yarn은 패키지를 설치할 때, 패키지 종속성과 최신 패키지 버전을 고려하여 설치할 패키지의 버전을 선택한다. ^ 은 메이저 버전은 유지하고 마이너 버전 이하에 대해서만 업데이트 하므로, 최신 버전인 react@18.3.1 역시 ^18.2.0 범위에 해당한다.

→ 따라서 lock 파일을 제거하고 yarn install을 수행하면, 패키지 매니저는 프로젝트들의 버전과 종속성을 모두 고려하여 최신 패키지 버전을 resolution 한다.

https://www.geeksforgeeks.org/difference-between-tilde-and-caret-in-package-json/

그렇다면 @shell/sdk 패키지를 업데이트 했을 때, react 패키지가 업데이트 되지 않고 추가 설치되는 이유는 무엇일까?

→ 근본적인 원인은 @shell/sdk 패키지를 만든 제작자가 external로 react를 명시하지 않았기 때문이다. 따라서 이러한 이슈는 사내 자체 개발한 패키지를 사용하는 등 매우 특수한 상황에서 겪을 수 있는 이슈라고 할 수 있다.

패키지 업데이트 이전에 **shell/sdk**가 사용한 react 버전을 확인해보면, 18.2.0인 것을 알 수 있었다.

undefined

undefined

이제 yarn up @shell/sdk@0.0.21 로 업데이트해보았다.

fetch step에서 react@18.3.1을 새로 설치하고 있음을 콘솔에서 확인 가능하다.

undefined

이미 기존 프로젝트에서 react@18.2.0 을 사용하며 해당 버전의 react를 의존성으로 패키지들이 관리되고 있는 상황에서 새로 업데이트하는 0.0.21 버전shell/sdk 는 18.3.1 react 버전을 의존성으로 하기 때문에 패키지 매니저는 기존 react 버전을 업데이트하는 것이 아닌, 새로운 버전의 react를 추가 설치하고 있는 상황이다.

프로젝트는 react@18.2.0을 필요로 하고, shell/sdkreact@18.3.1 을 필요로 하는 것이다. (즉, 둘 다 필요하다고 패키지 매니저는 인식하고 있다.)

다시 강조하지만, 외부 패키지 제작자가 external로 react를 명시해야 했음에도 그러지 않았던 것이 문제의 원인.

package.json에서 resolutions 필드에 버전을 명시하고 yarn up @shell/sdk@0.0.21 를 수행하면 fetch step에서 해당 문구는 사라진다. 이미 shell/sdk의 의존성으로 18.2.0 버전의 react가 resolution 되기 때문이다. 해당 버전의 react는 이미 cache 에 등록되어 있으므로 새로 설치되지 않는다.(이 프로젝트는 Yarn berry를 사용하고 있음에 유의, 패키지 매니저에 따라 패키지 버전을 resolution하는 필드 명이 다르다.)

정리

package.json에서 resolutions 필드를 이용한 방식은 프로젝트 내 새로운 패키지를 설치하거나 업데이트할 때 해당 패키지의 의존성을 resolutions 필드에서 정의한 버전으로 강제하는 방식이다.

https://classic.yarnpkg.com/lang/en/docs/selective-version-resolutions/

lock 파일 삭제 후 재설치는 프로젝트에서 이전에 설치한 패키지들의 버전과 의존성들을 새롭게 변경하여 적용하는 방식이며, 패키지 매니저에 따라 package.json에 명시한 버전 이내 패키지 버전 업데이트도 이루어질 수 있다. 이때 yarn.lock 파일이 변경되었으므로, 해당 커밋을 pull받은 다른 팀원은 yarn install을 수행해야 한다.


Profile picture

Written by Kim Soon Yo

IT 생태계의 플랑크톤

Github Link