Problem
Onyx has a race condition where write methods execute before Onyx.init() has completed, potentially corrupting or losing data.
How it works today
Onyx.init() initializes the storage backend, registers keys, loads initial key states from persistent storage, and resolves a deferredInitTask promise when done (lib/Onyx.ts L34-79).
- Read subscriptions (
Onyx.connect / useOnyx) already wait for init to complete -- they chain on deferredInitTask.promise before delivering values (lib/OnyxUtils.ts L1117).
- Write methods (
set, multiSet, merge, mergeCollection, clear, update, setCollection) do NOT wait for init. They execute immediately against whatever storage state exists, which may be uninitialized.
What can go wrong
- Writes against uninitialized storage -- if
Onyx.set() or Onyx.merge() is called before Onyx.init() finishes loading existing values from storage, the write may operate on empty/stale cache state.
- Race between init and writes -- the init process loads default key states and existing persisted data; a concurrent write could overwrite or conflict with this loading.
- Data loss -- values written before init may be silently overwritten when init's
initializeWithDefaultKeyStates() runs and populates the cache.
In the App, Onyx.init() is called in src/setup/index.ts during app bootstrap. While in normal app flow the init is fast enough that writes typically follow it, there is no guarantee -- especially in headless JS contexts (Android push notifications), slow storage, or during tests.
Solution
Add an afterInit() utility in OnyxUtils that gates execution on deferredInitTask -- if Onyx is already initialized it runs the action immediately (same microtask, critical for performance and React batching), otherwise it chains on the init promise. Wrap all Onyx write methods (set, multiSet, merge, mergeCollection, clear, update, setCollection) with afterInit() so they automatically defer until init completes.
Additionally, fix App test files that are missing Onyx.init() calls, which would now cause them to hang since writes wait for init that never comes.
Work is tracked in react-native-onyx PR #727 and App PR #81207.
Issue Owner
Current Issue Owner: @JmillsExpensify
Problem
Onyx has a race condition where write methods execute before
Onyx.init()has completed, potentially corrupting or losing data.How it works today
Onyx.init()initializes the storage backend, registers keys, loads initial key states from persistent storage, and resolves adeferredInitTaskpromise when done (lib/Onyx.tsL34-79).Onyx.connect/useOnyx) already wait for init to complete -- they chain ondeferredInitTask.promisebefore delivering values (lib/OnyxUtils.tsL1117).set,multiSet,merge,mergeCollection,clear,update,setCollection) do NOT wait for init. They execute immediately against whatever storage state exists, which may be uninitialized.What can go wrong
Onyx.set()orOnyx.merge()is called beforeOnyx.init()finishes loading existing values from storage, the write may operate on empty/stale cache state.initializeWithDefaultKeyStates()runs and populates the cache.In the App,
Onyx.init()is called insrc/setup/index.tsduring app bootstrap. While in normal app flow the init is fast enough that writes typically follow it, there is no guarantee -- especially in headless JS contexts (Android push notifications), slow storage, or during tests.Solution
Add an
afterInit()utility inOnyxUtilsthat gates execution ondeferredInitTask-- if Onyx is already initialized it runs the action immediately (same microtask, critical for performance and React batching), otherwise it chains on the init promise. Wrap all Onyx write methods (set,multiSet,merge,mergeCollection,clear,update,setCollection) withafterInit()so they automatically defer until init completes.Additionally, fix App test files that are missing
Onyx.init()calls, which would now cause them to hang since writes wait for init that never comes.Work is tracked in react-native-onyx PR #727 and App PR #81207.
Issue Owner
Current Issue Owner: @JmillsExpensify