fix: prevent stale state updates in useStellarBalance after unmount#10
fix: prevent stale state updates in useStellarBalance after unmount#10mertcicekci0 wants to merge 1 commit intostellar:mainfrom
Conversation
Add cancellation ref to skip state updates when the effect is cleaned up (component unmount or dependency change). Without this, in-flight RPC calls for balance/metadata continue resolving and call setState on an unmounted component, causing a memory leak and React warnings.
There was a problem hiding this comment.
Pull request overview
This PR adds a cancellation mechanism to the useStellarBalance React hook to prevent stale state updates when the component unmounts or the address prop changes while async RPC calls are in-flight. This follows the standard React pattern of using a ref-based cancellation flag tied to effect cleanup.
Changes:
- Introduces a
cancelledRef(viauseRef) that is set tofalsewhen the effect runs andtrueon cleanup - Guards
setStatecalls in bothrefreshBalanceandfetchAssetMetadatacallbacks withcancelledRef.currentchecks - Adds proper effect cleanup return function in the
useEffectthat triggers balance fetching
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Format the balance | ||
| const balanceFormatted = formatUnits(balanceRaw, decimals); | ||
|
|
||
| if (cancelledRef.current) return ""; |
There was a problem hiding this comment.
The cancellation guard at line 142 returns early from the try block, and the one at line 150 returns early from the catch block, but the finally block (line 160-162) still calls setIsFetchingBalance(false) unconditionally. Since finally always runs regardless of early returns, this state update will still fire after unmount/cancellation, undermining the purpose of these guards. You should add a cancelledRef.current check inside the finally block as well, or restructure so that the setIsFetchingBalance(false) call is placed before each return instead of in finally.
Summary
useStellarBalancehook fires async RPC calls (balance + decimals + metadata) in auseEffectbut has no cleanup — if the component unmounts oraddresschanges mid-flight, theresolved promises still call
setStateon a stale componentcancelledRefthat is set totrueon effect cleanup, checked before every state update in bothrefreshBalanceandfetchAssetMetadataChanges
packages/paywall/src/browser/useStellarBalance.tsuseRefcancellation flag + effect cleanupTest plan
pnpm --filter @x402-stellar/paywall typecheckpasses