While debugging a crash in @reown/appkit-react-native, the author uncovered that valtio proxies were breaking due to multiple library instances created by pnpm’s node-linker=hoisted setting. This caused each package to maintain separate WeakMap states, breaking shared behavior. The fix? Forcing all dependencies to use the same Valtio version via pnpm.overrides. The article also shares key debugging lessons: always check git history, clear caches when editing node_modules, and ensure consistent versions across your workspace to prevent multi-instance library conflicts.While debugging a crash in @reown/appkit-react-native, the author uncovered that valtio proxies were breaking due to multiple library instances created by pnpm’s node-linker=hoisted setting. This caused each package to maintain separate WeakMap states, breaking shared behavior. The fix? Forcing all dependencies to use the same Valtio version via pnpm.overrides. The article also shares key debugging lessons: always check git history, clear caches when editing node_modules, and ensure consistent versions across your workspace to prevent multi-instance library conflicts.

How Multiple Valtio Instances Broke My React Native App

2025/11/10 20:23
4분 읽기
이 콘텐츠에 대한 의견이나 우려 사항이 있으시면 crypto.news@mexc.com으로 연락주시기 바랍니다

It all started with a crash in @reown/appkit-react-native. While debugging that crash, I noticed something weird happening with valtio. The subscribe() function seemed to work fine, but proxy() wasn't behaving as expected. I decided to dig into the library code itself and added some debugging directly in node_modules/valtio/vanilla.js.

I printed proxyStateMap.get(proxyObject) in both functions, and that's when things got interesting:

  • In proxy(): Got the correct value ✓
  • In subscribe(): Got undefined

Wait, what? They were completely different WeakMap instances from different valtio module instances! The proxy object was being stored in one proxyStateMap, but when subscribe() tried to find it, it was looking in a completely different proxyStateMap from a different valtio instance. That's like putting your keys in one drawer and looking for them in another.

A Quick Note on Debugging Library Code

Before I continue, here's something I had to remember: when you're adding console.log statements to library code in node_modules, your logs might not show up because modules get cached. I spent way too long wondering why my logs weren't appearing before I remembered to run:

pnpm start --reset-cache

Adding logs directly to library code is actually super helpful when you're dealing with weird behavior - you can see exactly what's happening inside the library, trace the execution flow, and peek at internal state that's not exposed through the public API. Just don't forget to clear that cache!

Going Back in Time

At this point, I was stuck. The code was working before, so what changed? I did what I should have done from the start: I checked the git history.

I went through past commits one by one until I found a commit where everything worked. Then I compared the difference between the working state and the broken state. Bingo - there it was, a single line change in .npmrc:

+ node-linker=hoisted

That one line was the culprit.

What Actually Happened

After adding node-linker=hoisted, pnpm switched to a flat structure similar to npm, and that broke the sharing mechanism by creating separate valtio instances:

node_modules/valtio (version 1.13.2) node_modules/@reown/appkit-core-react-native/node_modules/valtio (version 2.1.8) node_modules/@reown/appkit-react-native/node_modules/valtio (version 2.1.8)

Why did this happen? A few reasons:

  1. Version mismatch: The root had 1.13.2 (some transitive dependency), while the nested packages needed 2.1.8
  2. No virtual store: With node-linker=hoisted, pnpm doesn't use the .pnpm virtual store structure that ensures proper symlinking
  3. pnpm kept them separate to avoid conflicts

Since they were using different valtio instances, and each instance had its own proxyStateMap WeakMap, the proxy object stored in one proxyStateMap wasn't found when subscribe() looked in a different proxyStateMap.

The Fix

The solution was to force all valtio dependencies to use the same version. I added this to my root package.json:

{ "pnpm": { "overrides": { "valtio": "2.1.8" } } }

Then ran pnpm install, and that was it. This:

  • Unified all valtio to 2.1.8
  • Allowed pnpm to hoist it to the root
  • Removed the nested instances
  • Made all packages share the same proxyStateMap

Alternatively, I could have just removed node-linker=hoisted to go back to pnpm's default virtual store, which handles this automatically. But I needed the hoisted structure for other reasons, so the override approach worked better for me.

Key Takeaways

  1. Check git history first - When something breaks, see what changed. The diff often reveals the root cause immediately.
  2. Adding console.log to library code is a good debugging habit - When you encounter weird library behavior, adding logs directly in node_modules can reveal exactly what's happening inside the library. Just remember to use --reset-cache when debugging library code in node_modules to see your changes.
  3. Multiple package instances can break singleton-like behavior - Libraries that rely on WeakMaps, module-level state, or singletons can fail when multiple instances exist. This isn't just a valtio thing - it can happen with any library that maintains internal state.
  4. Version mismatches prevent hoisting - Even with node-linker=hoisted, pnpm will keep separate instances if versions differ. Use pnpm.overrides to force consistent versions across your workspace.

Happy debugging!

시장 기회
Wrapped REACT 로고
Wrapped REACT 가격(REACT)
$0.02342
$0.02342$0.02342
+44.92%
USD
Wrapped REACT (REACT) 실시간 가격 차트
면책 조항: 본 사이트에 재게시된 글들은 공개 플랫폼에서 가져온 것으로 정보 제공 목적으로만 제공됩니다. 이는 반드시 MEXC의 견해를 반영하는 것은 아닙니다. 모든 권리는 원저자에게 있습니다. 제3자의 권리를 침해하는 콘텐츠가 있다고 판단될 경우, crypto.news@mexc.com으로 연락하여 삭제 요청을 해주시기 바랍니다. MEXC는 콘텐츠의 정확성, 완전성 또는 시의적절성에 대해 어떠한 보증도 하지 않으며, 제공된 정보에 기반하여 취해진 어떠한 조치에 대해서도 책임을 지지 않습니다. 본 콘텐츠는 금융, 법률 또는 기타 전문적인 조언을 구성하지 않으며, MEXC의 추천이나 보증으로 간주되어서는 안 됩니다.

$30,000 in PRL + 15,000 USDT

$30,000 in PRL + 15,000 USDT$30,000 in PRL + 15,000 USDT

Deposit & trade PRL to boost your rewards!