-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #56891 from Expensify/Rory-OnyxDerivedV2
Onyx derived values
- Loading branch information
Showing
19 changed files
with
292 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
import type {ValueOf} from 'type-fest'; | ||
import ONYXKEYS from '@src/ONYXKEYS'; | ||
import conciergeChatReportIDConfig from './configs/conciergeChatReportID'; | ||
import type {OnyxDerivedValueConfig} from './types'; | ||
|
||
/** | ||
* Global map of derived configs. | ||
* This object holds our derived value configurations. | ||
*/ | ||
const ONYX_DERIVED_VALUES = { | ||
[ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID]: conciergeChatReportIDConfig, | ||
} as const satisfies { | ||
// eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
[Key in ValueOf<typeof ONYXKEYS.DERIVED>]: OnyxDerivedValueConfig<Key, any>; | ||
}; | ||
|
||
export default ONYX_DERIVED_VALUES; |
29 changes: 29 additions & 0 deletions
29
src/libs/actions/OnyxDerived/configs/conciergeChatReportID.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import {isThread} from '@libs/ReportUtils'; | ||
import createOnyxDerivedValueConfig from '@userActions/OnyxDerived/createOnyxDerivedValueConfig'; | ||
import CONST from '@src/CONST'; | ||
import ONYXKEYS from '@src/ONYXKEYS'; | ||
|
||
export default createOnyxDerivedValueConfig({ | ||
key: ONYXKEYS.DERIVED.CONCIERGE_CHAT_REPORT_ID, | ||
dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID], | ||
compute: ([reports, conciergeChatReportID]) => { | ||
if (!reports) { | ||
return undefined; | ||
} | ||
|
||
const conciergeReport = Object.values(reports).find((report) => { | ||
if (!report?.participants || isThread(report)) { | ||
return false; | ||
} | ||
|
||
const participantAccountIDs = new Set(Object.keys(report.participants)); | ||
if (participantAccountIDs.size !== 2) { | ||
return false; | ||
} | ||
|
||
return participantAccountIDs.has(CONST.ACCOUNT_ID.CONCIERGE.toString()) || report?.reportID === conciergeChatReportID; | ||
}); | ||
|
||
return conciergeReport?.reportID; | ||
}, | ||
}); |
24 changes: 24 additions & 0 deletions
24
src/libs/actions/OnyxDerived/createOnyxDerivedValueConfig.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
import type {NonEmptyTuple, ValueOf} from 'type-fest'; | ||
import type {OnyxKey} from '@src/ONYXKEYS'; | ||
import type ONYXKEYS from '@src/ONYXKEYS'; | ||
import type {OnyxDerivedValueConfig} from './types'; | ||
|
||
/** | ||
* Helper function to create a derived value config. This function is just here to help TypeScript infer Deps, so instead of writing this: | ||
* | ||
* const conciergeChatReportIDConfig: OnyxDerivedValueConfig<[typeof ONYXKEYS.COLLECTION.REPORT, typeof ONYXKEYS.CONCIERGE_REPORT_ID]> = { | ||
* dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID], | ||
* ... | ||
* }; | ||
* | ||
* We can just write this: | ||
* | ||
* const conciergeChatReportIDConfig = createOnyxDerivedValueConfig({ | ||
* dependencies: [ONYXKEYS.COLLECTION.REPORT, ONYXKEYS.CONCIERGE_REPORT_ID] | ||
* }) | ||
*/ | ||
export default function createOnyxDerivedValueConfig<Key extends ValueOf<typeof ONYXKEYS.DERIVED>, Deps extends NonEmptyTuple<Exclude<OnyxKey, Key>>>( | ||
config: OnyxDerivedValueConfig<Key, Deps>, | ||
): OnyxDerivedValueConfig<Key, Deps> { | ||
return config; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
/** | ||
* This file contains logic for derived Onyx keys. The idea behind derived keys is that if there is a common computation | ||
* that we're doing in many places across the app to derive some value from multiple Onyx values, we can move that | ||
* computation into this file, run it only once, and then share it across the app by storing the result of that computation in Onyx. | ||
* | ||
* The primary purpose is to optimize performance by reducing redundant computations. More info can be found in the README. | ||
*/ | ||
import Onyx from 'react-native-onyx'; | ||
import OnyxUtils from 'react-native-onyx/dist/OnyxUtils'; | ||
import Log from '@libs/Log'; | ||
import ObjectUtils from '@src/types/utils/ObjectUtils'; | ||
import ONYX_DERIVED_VALUES from './ONYX_DERIVED_VALUES'; | ||
|
||
/** | ||
* Initialize all Onyx derived values, store them in Onyx, and setup listeners to update them when dependencies change. | ||
*/ | ||
function init() { | ||
for (const [key, {compute, dependencies}] of ObjectUtils.typedEntries(ONYX_DERIVED_VALUES)) { | ||
// Create an array to hold the current values for each dependency. | ||
// We cast its type to match the tuple expected by config.compute. | ||
let dependencyValues = new Array(dependencies.length) as Parameters<typeof compute>[0]; | ||
|
||
OnyxUtils.get(key).then((storedDerivedValue) => { | ||
let derivedValue = storedDerivedValue; | ||
if (derivedValue) { | ||
Log.info(`Derived value ${derivedValue} for ${key} restored from disk`); | ||
} else { | ||
OnyxUtils.tupleGet(dependencies).then((values) => { | ||
dependencyValues = values; | ||
derivedValue = compute(values, derivedValue); | ||
Onyx.set(key, derivedValue ?? null); | ||
}); | ||
} | ||
|
||
const setDependencyValue = <Index extends number>(i: Index, value: Parameters<typeof compute>[0][Index]) => { | ||
dependencyValues[i] = value; | ||
}; | ||
|
||
const recomputeDerivedValue = () => { | ||
const newDerivedValue = compute(dependencyValues, derivedValue); | ||
if (newDerivedValue !== derivedValue) { | ||
Log.info(`[OnyxDerived] value for key ${key} changed, updating it in Onyx`, false, {old: derivedValue ?? null, new: newDerivedValue ?? null}); | ||
derivedValue = newDerivedValue; | ||
Onyx.set(key, derivedValue ?? null); | ||
} | ||
}; | ||
|
||
for (let i = 0; i < dependencies.length; i++) { | ||
// eslint-disable-next-line rulesdir/prefer-at | ||
const dependencyOnyxKey = dependencies[i]; | ||
if (OnyxUtils.isCollectionKey(dependencyOnyxKey)) { | ||
Onyx.connect({ | ||
key: dependencyOnyxKey, | ||
waitForCollectionCallback: true, | ||
callback: (value) => { | ||
Log.info(`[OnyxDerived] dependency ${dependencyOnyxKey} for derived key ${key} changed, recomputing`); | ||
setDependencyValue(i, value); | ||
recomputeDerivedValue(); | ||
}, | ||
}); | ||
} else { | ||
Onyx.connect({ | ||
key: dependencyOnyxKey, | ||
callback: (value) => { | ||
Log.info(`[OnyxDerived] dependency ${dependencyOnyxKey} for derived key ${key} changed, recomputing`); | ||
setDependencyValue(i, value); | ||
recomputeDerivedValue(); | ||
}, | ||
}); | ||
} | ||
} | ||
}); | ||
} | ||
} | ||
|
||
export default init; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import type {OnyxValue} from 'react-native-onyx'; | ||
import type {NonEmptyTuple, ValueOf} from 'type-fest'; | ||
import type {OnyxDerivedValuesMapping, OnyxKey} from '@src/ONYXKEYS'; | ||
import type ONYXKEYS from '@src/ONYXKEYS'; | ||
|
||
/** | ||
* A derived value configuration describes: | ||
* - a tuple of Onyx keys to subscribe to (dependencies), | ||
* - a compute function that derives a value from the dependent Onyx values. | ||
* The compute function receives a single argument that's a tuple of the onyx values for the declared dependencies. | ||
* For example, if your dependencies are `['report_', 'account'], then compute will receive a [OnyxCollection<Report>, OnyxEntry<Account>] | ||
*/ | ||
type OnyxDerivedValueConfig<Key extends ValueOf<typeof ONYXKEYS.DERIVED>, Deps extends NonEmptyTuple<Exclude<OnyxKey, Key>>> = { | ||
key: Key; | ||
dependencies: Deps; | ||
compute: ( | ||
args: { | ||
[Index in keyof Deps]: OnyxValue<Deps[Index]>; | ||
}, | ||
currentValue: OnyxValue<Key>, | ||
) => OnyxDerivedValuesMapping[Key]; | ||
}; | ||
|
||
// eslint-disable-next-line import/prefer-default-export | ||
export type {OnyxDerivedValueConfig}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.