@@ -114,9 +114,12 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
114114 } ,
115115 ] ) ;
116116
117- // Indicates if it's the first Onyx connection of this hook or not, as we don't want certain use cases
118- // in `getSnapshot()` to be satisfied several times.
119- const isFirstConnectionRef = useRef ( true ) ;
117+ // Tracks which key has completed its first Onyx connection callback. When this doesn't match the
118+ // current key, getSnapshot() treats the hook as being in its "first connection" state for that key.
119+ // This is key-aware by design: when the key changes, connectedKeyRef still holds the old key (or null
120+ // after cleanup), so the hook automatically enters first-connection mode for the new key without any
121+ // explicit reset logic — eliminating the race condition where cleanup could clobber a boolean flag.
122+ const connectedKeyRef = useRef < OnyxKey | null > ( null ) ;
120123
121124 // Indicates if the hook is connecting to an Onyx key.
122125 const isConnectingRef = useRef ( false ) ;
@@ -130,17 +133,6 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
130133 // Inside useOnyx.ts, we need to track the sourceValue separately
131134 const sourceValueRef = useRef < NonNullable < TReturnValue > | undefined > ( undefined ) ;
132135
133- // When the key changes, reset internal state so the hook properly transitions through loading
134- // for the new key instead of preserving stale status from the previous key.
135- if ( key !== previousKey ) {
136- previousValueRef . current = null ;
137- newValueRef . current = null ;
138- isFirstConnectionRef . current = true ;
139- shouldGetCachedValueRef . current = true ;
140- sourceValueRef . current = undefined ;
141- resultRef . current = [ undefined , { status : options ?. initWithStoredValues === false ? 'loaded' : 'loading' } ] ;
142- }
143-
144136 // Cache the options key to avoid regenerating it every getSnapshot call
145137 const cacheKey = useMemo (
146138 ( ) =>
@@ -169,7 +161,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
169161
170162 previousDependenciesRef . current = dependencies ;
171163
172- if ( connectionRef . current === null || isConnectingRef . current || ! onStoreChangeFnRef . current ) {
164+ if ( connectionRef . current === null || isConnectingRef . current || connectedKeyRef . current !== key || ! onStoreChangeFnRef . current ) {
173165 return ;
174166 }
175167
@@ -205,7 +197,8 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
205197 // Check if we have any cache for this Onyx key
206198 // Don't use cache for first connection with initWithStoredValues: false
207199 // Also don't use cache during active data updates (when shouldGetCachedValueRef is true)
208- if ( ! ( isFirstConnectionRef . current && options ?. initWithStoredValues === false ) && ! shouldGetCachedValueRef . current ) {
200+ const isFirstConnection = connectedKeyRef . current !== key ;
201+ if ( ! ( isFirstConnection && options ?. initWithStoredValues === false ) && ! shouldGetCachedValueRef . current ) {
209202 const cachedResult = onyxSnapshotCache . getCachedResult < UseOnyxResult < TReturnValue > > ( key , cacheKey ) ;
210203 if ( cachedResult !== undefined ) {
211204 resultRef . current = cachedResult ;
@@ -214,7 +207,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
214207 }
215208
216209 // We return the initial result right away during the first connection if `initWithStoredValues` is set to `false`.
217- if ( isFirstConnectionRef . current && options ?. initWithStoredValues === false ) {
210+ if ( isFirstConnection && options ?. initWithStoredValues === false ) {
218211 const result = resultRef . current ;
219212
220213 // Store result in snapshot cache
@@ -226,7 +219,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
226219 // so we can return any cached value right away. For the case where the key has changed, If we don't return the cached value right away, then the UI will show the incorrect (previous) value for a brief period which looks like a UI glitch to the user. After the connection is made, we only
227220 // update `newValueRef` when `Onyx.connect()` callback is fired.
228221 const hasSelectorChanged = lastComputedSelectorRef . current !== memoizedSelector ;
229- if ( isFirstConnectionRef . current || shouldGetCachedValueRef . current || key !== previousKey || hasSelectorChanged ) {
222+ if ( isFirstConnection || shouldGetCachedValueRef . current || key !== previousKey || hasSelectorChanged ) {
230223 // Gets the value from cache and maps it with selector. It changes `null` to `undefined` for `useOnyx` compatibility.
231224 const value = OnyxUtils . tryGetCachedValue ( key ) as OnyxValue < TKey > ;
232225 const selectedValue = memoizedSelector ? memoizedSelector ( value ) : value ;
@@ -245,7 +238,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
245238
246239 // If we have pending merge operations for the key during the first connection, we set the new value to `undefined`
247240 // and fetch status to `loading` to simulate that it is still being loaded until we have the most updated data.
248- if ( isFirstConnectionRef . current && OnyxUtils . hasPendingMergeForKey ( key ) ) {
241+ if ( isFirstConnection && OnyxUtils . hasPendingMergeForKey ( key ) ) {
249242 newValueRef . current = undefined ;
250243 newFetchStatus = 'loading' ;
251244 }
@@ -270,7 +263,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
270263 // OR we have a pending `Onyx.clear()` task (if `Onyx.clear()` is running cache might not be available anymore
271264 // OR the subscriber is triggered (the value is gotten from the storage)
272265 // so we update the cached value/result right away in order to prevent infinite loading state issues).
273- const shouldUpdateResult = ! areValuesEqual || ( previousValueRef . current === null && ( hasCacheForKey || OnyxCache . hasPendingTask ( TASK . CLEAR ) || ! isFirstConnectionRef . current ) ) ;
266+ const shouldUpdateResult = ! areValuesEqual || ( previousValueRef . current === null && ( hasCacheForKey || OnyxCache . hasPendingTask ( TASK . CLEAR ) || ! isFirstConnection ) ) ;
274267 if ( shouldUpdateResult ) {
275268 previousValueRef . current = newValueRef . current ;
276269
@@ -294,6 +287,14 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
294287
295288 const subscribe = useCallback (
296289 ( onStoreChange : ( ) => void ) => {
290+ // Reset internal state so the hook properly transitions through loading
291+ // for the new key instead of preserving stale state from the previous one.
292+ previousValueRef . current = null ;
293+ newValueRef . current = null ;
294+ shouldGetCachedValueRef . current = true ;
295+ sourceValueRef . current = undefined ;
296+ resultRef . current = [ undefined , { status : options ?. initWithStoredValues === false ? 'loaded' : 'loading' } ] ;
297+
297298 isConnectingRef . current = true ;
298299 onStoreChangeFnRef . current = onStoreChange ;
299300
@@ -303,9 +304,9 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
303304 isConnectingRef . current = false ;
304305 onStoreChangeFnRef . current = onStoreChange ;
305306
306- // Signals that the first connection was made, so some logics in `getSnapshot()`
307- // won't be executed anymore.
308- isFirstConnectionRef . current = false ;
307+ // Signals that the first connection was made for this key , so some logics
308+ // in `getSnapshot()` won't be executed anymore.
309+ connectedKeyRef . current = key ;
309310
310311 // Signals that we want to get the newest cached value again in `getSnapshot()`.
311312 shouldGetCachedValueRef . current = true ;
@@ -332,7 +333,7 @@ function useOnyx<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>>(
332333 }
333334
334335 connectionManager . disconnect ( connectionRef . current ) ;
335- isFirstConnectionRef . current = false ;
336+ connectedKeyRef . current = null ;
336337 isConnectingRef . current = false ;
337338 onStoreChangeFnRef . current = null ;
338339 } ;
0 commit comments