Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion lib/useOnyx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@
import onyxSnapshotCache from './OnyxSnapshotCache';
import useLiveRef from './useLiveRef';

function noopUnsubscribe(): void {
// No-op: do nothing when unsubscribing from a non-subscribed state
}

type UseOnyxSelector<TKey extends OnyxKey, TReturnValue = OnyxValue<TKey>> = (data: OnyxValue<TKey> | undefined) => TReturnValue;

type UseOnyxOptions<TKey extends OnyxKey, TReturnValue> = {
Expand Down Expand Up @@ -58,6 +62,14 @@
* @see `useOnyx` cannot return `null` and so selector will replace `null` with `undefined` to maintain compatibility.
*/
selector?: UseOnyxSelector<TKey, TReturnValue>;

/**
* Controls whether the hook subscribes to Onyx updates.
* When set to `false`, the hook will not listen for updates and won't trigger re-renders.
* When it changes from `false` to `true`, the hook will re-subscribe and fetch the latest data.
* Defaults to `true` if not specified.
*/
subscribed?: boolean;
};

type FetchStatus = 'loading' | 'loaded';
Expand All @@ -80,6 +92,13 @@
const currentDependenciesRef = useLiveRef(dependencies);
const selector = options?.selector;

// // Compute subscribed state once (defaults to true for backward compatibility)
const isSubscribed = options?.subscribed !== false;
const previousSubscribedRef = useRef(isSubscribed);

// // Track if we're transitioning from false to true to force fresh data read
const isResubscribing = !previousSubscribedRef.current && isSubscribed;
previousSubscribedRef.current = isSubscribed;
// Create memoized version of selector for performance
const memoizedSelector = useMemo(() => {
if (!selector) {
Expand Down Expand Up @@ -230,6 +249,18 @@
}, [key, options?.canEvict]);

const getSnapshot = useCallback(() => {
// When not subscribed, return the last known value to prevent re-renders
// This ensures that when subscribed becomes false, we don't trigger unnecessary re-renders
if (!isSubscribed) {
return resultRef.current;
}

// When resubscribing (transitioning from false to true), force reading fresh data
// This prevents returning stale data and causing an extra re-render
if (isResubscribing) {
shouldGetCachedValueRef.current = true;
}

// Check if we have any cache for this Onyx key
// Don't use cache for first connection with initWithStoredValues: false
// Also don't use cache during active data updates (when shouldGetCachedValueRef is true)
Expand Down Expand Up @@ -330,16 +361,28 @@
}

return resultRef.current;
}, [options?.initWithStoredValues, options?.allowStaleData, options?.canBeMissing, key, memoizedSelector, cacheKey, previousKey]);

Check warning on line 364 in lib/useOnyx.ts

View workflow job for this annotation

GitHub Actions / lint

React Hook useCallback has missing dependencies: 'isResubscribing' and 'isSubscribed'. Either include them or remove the dependency array

const subscribe = useCallback(
(onStoreChange: () => void) => {
// If subscribed is false, return a no-op unsubscribe function
// This prevents the hook from subscribing to Onyx updates
if (!isSubscribed) {
return noopUnsubscribe;
}

isConnectingRef.current = true;
onStoreChangeFnRef.current = onStoreChange;

connectionRef.current = connectionManager.connect<CollectionKeyBase>({
key,
callback: (value, callbackKey, sourceValue) => {
// Only process callbacks if we're still subscribed
// This check protects against race conditions where subscribed changes
// but a callback is already queued
if (!isSubscribed) {
return;
}

isConnectingRef.current = false;
onStoreChangeFnRef.current = onStoreChange;

Expand Down Expand Up @@ -377,7 +420,7 @@
onStoreChangeFnRef.current = null;
};
},
[key, options?.initWithStoredValues, options?.reuseConnection, checkEvictableKey],

Check warning on line 423 in lib/useOnyx.ts

View workflow job for this annotation

GitHub Actions / lint

React Hook useCallback has a missing dependency: 'isSubscribed'. Either include it or remove the dependency array
);

const getSnapshotDecorated = useMemo(() => {
Expand Down
Loading