This is how the application manages all the data stored in Onyx.
- Actions - The files stored in
/src/libs/actions - Derived Values - Special Onyx keys containing values computed from other Onyx values
- Collections - Multiple related data objects stored as individual keys with IDs
This improves performance and lessens the chance that one action will overwrite the changes made by another action.
- Data SHOULD be optimistically stored on disk whenever possible without waiting for a server response
Example of creating a new optimistic comment:
- User adds a comment
- Comment is shown immediately in the UI with optimistic data
- Comment is created in the server
- Server responds
- UI updates with data from the server
Store collections as individual keys+ID (e.g., report_1234, report_4567) when a component will bind directly to one of those keys. For example: reports are stored as individual keys because OptionRow.js binds to the individual report keys for each link. However, report actions are stored as an array of objects because nothing binds directly to a single report action.
Each Onyx key represents either a collection of items or a specific entry in storage. For example, since all reports are stored as individual keys like report_1234, if code needs to know about all the reports (e.g., display a list of them in the nav menu), then it MUST subscribe to the key ONYXKEYS.COLLECTION.REPORT.
Different platforms come with varying storage capacities and Onyx has a way to gracefully fail when those storage limits are encountered.
To flag a key as safe for removal:
- Add the key to the
evictableKeysoption inOnyx.init(options) - Implement
canEvictin the Onyx config for each component subscribing to a key - The key will only be deleted when all subscribers return
trueforcanEvict
Example:
Onyx.init({
evictableKeys: [ONYXKEYS.COLLECTION.REPORT_ACTIONS],
});
const [reportActions] = useOnyx(`${ONYXKEYS.COLLECTION.REPORT_ACTIONS}${reportID}`, {canEvict: !isActiveReport});Derived values are special Onyx keys which contain values derived from other Onyx values. These are available as a performance optimization, so that if the result of a common computation of Onyx values is needed in many places across the app, the computation can be done only as needed in a centralized location, and then shared across the app. Once created, Onyx derived values are stored and consumed just like any other Onyx value.
When multiple components need the same computed value from one or more Onyx keys, and:
- The computation is expensive (e.g., filtering large arrays, complex object transformations)
- The result needs to be cached and shared to avoid redundant calculations
- The computation appears in frequently rendered components
- Profiling shows the same calculation being done repeatedly
When you need to:
- Combine data from multiple Onyx keys into a single, normalized structure
- The transformation logic is complex and reusable
- The derived data structure is used in multiple places
- The value depends on multiple pieces of state that can change independently
Avoid derived values when:
- The computation is trivial (e.g., simple string manipulation, basic math)
- The value is only used in one component
- The computation is specific to a single component's UI state
- The logic involves component-local state
- The computed value is only needed temporarily
- The computation depends on non-Onyx values
- Add the new Onyx key to
ONYXKEYS.tsin theONYXKEYS.DERIVEDobject - Declare the type for the derived value in
ONYXKEYS.tsin theOnyxDerivedValuesMappingtype - Add the derived value config to
ONYX_DERIVED_VALUESinsrc/libs/OnyxDerived.ts
A derived value config MUST include:
- The Onyx key for the derived value
- An array of Onyx key dependencies (which can be any keys, including other derived values)
- A
computefunction that takes an array of Onyx values for the dependencies and returns a derived value matching the declared type
// GOOD ✅
compute: ([reports, personalDetails]) => {
// Pure function, only depends on input
return reports.map(report => ({
...report,
authorName: personalDetails[report.authorID]?.displayName
}));
}
// BAD ❌
compute: ([reports]) => {
// Don't use external state or cause side effects
const currentUser = getCurrentUser(); // External dependency!
sendAnalytics('computation-done'); // Side effect!
return reports;
}// GOOD ✅
compute: ([reports, personalDetails]: [Report[], PersonalDetails]): DerivedType => {
if (!reports?.length || !personalDetails) {
return { items: [], count: 0 };
}
// Rest of computation...
}
// BAD ❌
compute: ([reports, personalDetails]) => {
// Missing type safety and edge cases
return reports.map(report => personalDetails[report.id]);
}- Explain the purpose and dependencies
- Document any special cases or performance considerations
- Include type annotations for better developer experience