-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add initialValue, delayUpdates to allow components to eagerly render. #329
Add initialValue, delayUpdates to allow components to eagerly render. #329
Conversation
CLA Assistant Lite bot All contributors have signed the CLA ✍️ ✅ |
I have read the CLA Document and I hereby sign the CLA |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we undo the whitespace and quotation changes so this PR is a lot less noisy?
also cc @marcaaron and @tgolen who might be interested in having a peek at this.
lib/Onyx.js
Outdated
import fastMerge from './fastMerge'; | ||
import * as PerformanceUtils from './metrics/PerformanceUtils'; | ||
import Storage from './storage'; | ||
import { deepEqual } from "fast-equals"; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A lot of updates here that deviate from our style guide. Can we re-update to get these back to that standard?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, I just left prettier do it's thing, what is the configuration I should use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cleaned all the formatting changes, should be a lot let noisy now
lib/Onyx.js
Outdated
const getSubsetOfData = (sourceData, selector, withOnyxInstanceState) => | ||
selector(sourceData, withOnyxInstanceState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also question if updates like this adding new lines for lines that weren't already that long are necessary. They're adding a lot of noise to this PR IMO.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I cleaned all the formatting changes, should be a lot let noisy now
lib/utils.js
Outdated
@@ -0,0 +1,3 @@ | |||
export function areObjectsEmpty(a, b) { | |||
return typeof data === 'object' && typeof previousData === 'object' && Object.values(a).length === 0 && Object.values(b).length === 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where do data
and previous
data come from and what is this method achieving exactly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right, this was a copy and paste mistake from my other PR, the correct values are a
and b
Sorry I have been reviewing a lot of Onyx PRs and can't prioritize a review today as these changes looks quite involved. Also +1 to @yuwenmemon's comment about formatting. It looks like prettier ran with the wrong settings maybe. First, thanks for the work @ospfranco. Excited to discuss everything with you. I will leave some questions and suggestions regarding the PR description as there are some things I'm not really clear on and wouldn't really be able to review the changes without understanding them first.
Where exactly was the bottleneck and what did the analysis show? Please add this to the description.
Yes, if you have a lot of subscribers then they will re-render when there is a change. Is there some specific example of this that showcases the problem better? Did we discuss any alternatives?
@hannojg's PR to use the cached values was helping with this part. Is it not enough? Where does this delay happen? What does it feel like?
Any discussion to link? I am not sure if it's clear that we want or need anything like an "initial value" to be added to the Onyx API.
Is the hook something we already have or are adding here? I didn't see it in the PR but only looked briefly. Overall, this idea sounds kind of neat, but also sounds like added complexity. Also, this might be semantics, but I think
Is this related to the current PR? |
Components with a lot of connections are too slow, e.g. ReportScreen, this container grew and is now connected to a lot of entities. As onyx updates the component state via HOC, more updates are fetched from the DB or API responses come back, this causes the component to be constantly re-render, sometimes it delays the initial rendering and almost always causes subsequent re-renders. In the case of the ReportScreen being this a container on top of the tree, each re-render is very expensive and delays everything, from screen transition animation, to the list of actions rendering.
All the changes were done when testing the ReportScreen and that is the best example. You can see on this line on the PR on expensify, one solution we had to come up to keep the animation running smoothly is to delay the update of the component, after is has mounted and has been layed out, then allow onyx to start the many queued state changes of all the data is has pending. The only other solution we considered might have an impact was the batching of updates, as it avoid frequent re-renders by batching the updates, but that PR/discussion is still pending.
No, it is not enough, once values are in cache it surely does help, but on first mount/render and on a large-subtree with lots connections it causes delays in many components, in total delaying the transition/first render. By adding the initialValue prop, we can allow the component to render as fast as possible, while Onyx is fetching stuff from DB. The state might not be always consistent with the data, but it is rather not an issue for most components. Again, the best example is the entire ReportScreen rendering that hang-ups while the many re-renders ocurr.
No, all the discussions were internal. Here is an example of a Performance trace in flipper: It's hard to explain, but basically, the first delay, plus frequent sub-sequent state updates simply do not allow React to flush and therefore delay everything until everything settles down.
It is something added. I cleaned the PR, it should be easier to spot now. You can remove it, but then you will always have the same problem as components grow over time. Fetching from DB/API will not allow them to mount on first render and screen transitions/mounting will be delayed until onyx reaches a stable state.
Yes, it does have a big impact on screen transitions. Just to further make the point, here is the ReportScreen transition, before and after hour changes: Most of the performance gains came from adding initialValues to many smaller components and the delayUpdate of the big container: IMG_9525.MOVIMG_9526.MOV |
Linting on utils
Thanks for all that information.
Can you please add before/after traces so it can be directly compared? Another thing that would be useful is specific timings for what "delayed" means. Is it seconds? milliseconds? microseconds? When we do anything related to performance there should always be measurements of before and after to quantify and demonstrate the improvement.
Could you please link to a PR (it's fine if it's only a draft) of App with these changes so I can see what the |
lib/utils.js
Outdated
return ( | ||
typeof a === 'object' | ||
&& typeof b === 'object' | ||
&& _.values(a).length === 0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the difference between this and using _. isEmpty_()
or _.size() === 0
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial implementation was Object.values(a).length, linter barked at me I should use underscore, no other reason. I will change it to _.isEmpty
@@ -0,0 +1,12 @@ | |||
import * as _ from 'underscore'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that there is a utils.js
file, is there more stuff that can be moved here? Maybe the fast merge stuff?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, makes sense.
@@ -0,0 +1,12 @@ | |||
import * as _ from 'underscore'; | |||
|
|||
function areObjectsEmpty(a, b) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this implementation is too specific in that it requires two objects. I think there are already existing methods to check if a single object is empty, and then it's just as easy as doing if (_.isEmpty(a) && _.isEmpty(b))
and then it makes this utility method go away.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_.isEmpty(8)
is giving me true
. Because we don't know what Onyx is sending to the connection, we only really want to do this check to avoid unnecessary re-renders on objects (and maybe arrays? I didn't see any array while debugging)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are some arrays, but they are less frequently used.
This one comes to mind: https://github.com/Expensify/App/blob/48437acb34dbc38b9b7a60dae4c272727b8ce3ab/src/libs/actions/ActiveClients.js#L9
lib/withOnyx.js
Outdated
render() { | ||
// Remove any null values so that React replaces them with default props | ||
const propsToPass = _.omit(this.props, value => _.isNull(value)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work?
const propsToPass = _.omit(this.props, value => _.isNull(value)); | |
const propsToPass = _.omit(this.props, _.isNullvalue); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
only passing _.isNull should work
const key = Str.result(mapping.key, props); | ||
let value = Onyx.tryGetCachedValue(key, mapping); | ||
if (!value && mapping.initialValue) { | ||
value = mapping.initialValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there any way to just get the default value out of the existing defaultProps
? It feels very redundant to me to have both.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good idea, I'm not sure, but I will take a look
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about this, and I'm not sure it is the best idea. I really prefer explicit behaviors to implicit ones. By applying this change all the loading states from components that use the withOnyx
hook will change, and probably no loading states will be shown any more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW if we end up implementing a hook for Onyx then we don't really need defaultProps
at all as the values are not props anymore. The interaction becomes simpler because you can:
- Stop using
withOnyx()
- Delay the rendering (or not) at your discretion
- No more defaultProps
// I am not a prop. Find some other way to validate my type 🙃
const {value: allReports} = useOnyx({key: 'reports_', initialValue: {}});
if (empty(allReports)) {
return null;
}
oh yea, also, the TS migration appears to have proposed we do away with defaultProps
entirely
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the hook though - to me, this begs the question of whether the initialValue
should later be restored if the value becomes undefined
in the future. It's an "initial value", but easy to confuse it as a "default value".
Should I still provide a default via props or default props? In this case, yes. It is only used for setting the initial value.
Thanks for the writeup @ospfranco ! This might be a naive interpretation of the problem, but we can reduce it to: it takes time to rehydrate state from storage and this blocks rendering It looks like these delays are the cost of loading from storage into memory lazily. So, one thought that comes to mind is whether we can solve this by prioritizing the work to hydrate the state earlier? If the app was burdened with less data requirements and could rehydrate the critical state faster - would we still need this solution? I am curious about proceeding here without an answer to that question though not trying to block on this if @yuwenmemon and @tgolen feel more confident than me. From what I can tell the interactions will be faster, but the UX also janky because we load an initial value -> stored value later -> maybe API stuff later. Probably we will want to smooth that out with a loading state of some kind, but having faster interactions is a net positive change. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few more notes to reduce noise even more
@marcaaron pre-loading/pre-fetching is a solution, but it is not always applicable or practical. For example, action reactions are not really linked to anything and it is not possible to know before hand which ones can be pre-loaded. Other entities might require loading the entire db into memory to be really effective. In any case, this is rather a techinique for further optimization and the current implemented solution allows for a more generic solution, which is allowing the components to render while state is being fetched. |
Thanks for continuing the discussion with me @ospfranco!
The reactions are something we could prefetch. They are linked to the actions via We could also layer something like this over your proposed changes later to improve the UX so I'd love to hear your thoughts on this idea either way. Which data are we trying to load specifically that is causing a delay? Is it all stuff we need? Why is it slow? Can we do anything to load it faster?
Which entity? I feel like we are talking about some hypothetical mega component that for some reason needs the entire database in memory and actually can't think of what that would be.
Overall, this looks like a good idea that we probably need. I'm just trying to understand if we need it right now. It being a "generic solution" is part of my concern. Generic solutions sometimes add complexity or cause regressions for not much obvious benefit. It does also feel like we may end up with 99% of cases where we're not gonna need Thanks! |
Ah sorry, I thought reactions where only based on the actionID. The point I'm trying to make is rather: before the user clicks on the report you don't really know what to pre-fetch. In order to make it really effective we would have to load the entire DB into memory. Once the report is opened we basically load everything, it's done via different components ofc, but it ends up being the entire sub-tree of data linked to the clicked Report. Report, ReportMetadata, Actions, Reactions to those Actions, IOUReports, etc. Each one causes a delay at different components and times. And yes, it is all needed, we got rid of a couple of unneeded connections on the corresponding Expensify PR but nothing major. As far as I can see there is nothing to be done to load it faster.
Right now the ReportScreen is the biggest one, but as far as I'm told, into only grew in complexity somewhat recently. However, as time passes it is the natural progression more and more components will get connected to more entities. The problem is not only one single component, but the entire sub-tree that gets rendered. It's on a case to case basis that sub-trees get expensive to render or cause a lot of re-renders. It's not about one entity, but rather how onyx holds rendering/pushes states and the interaction between components and React trying to flush/render components.
As stated, we could not apply pre-fetching to the ReportScreen and it's entire sub-tree of components that mounted and caused delays in rendering. The only solution we found was to allow the components to eagerly render. If we do not merge this change, we only apply superficial optimizations but the entire sub-tree will block as onyx pushes updates to the components.
Unless you load the entire DB into cache, I believe it is impossible to predict a cache miss. As
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Couple small things / questions. I am more ok with the idea as a whole now after reviewing more thoroughly. Though, still kind of wonder about the behavior of initialValue
and if it should maybe replace defaultValue
.
const key = Str.result(mapping.key, props); | ||
let value = Onyx.tryGetCachedValue(key, mapping); | ||
if (!value && mapping.initialValue) { | ||
value = mapping.initialValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW if we end up implementing a hook for Onyx then we don't really need defaultProps
at all as the values are not props anymore. The interaction becomes simpler because you can:
- Stop using
withOnyx()
- Delay the rendering (or not) at your discretion
- No more defaultProps
// I am not a prop. Find some other way to validate my type 🙃
const {value: allReports} = useOnyx({key: 'reports_', initialValue: {}});
if (empty(allReports)) {
return null;
}
oh yea, also, the TS migration appears to have proposed we do away with defaultProps
entirely
// This handles case when setState was called before the setWithOnyxState. | ||
// For example, when an Onyx property was updated by keyChanged before the call of the setWithOnyxState. | ||
this.setState((prevState) => { | ||
const remainingTempState = _.omit(this.tempState, _.keys(prevState)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we reintroducing a bug with this change? Seems like the previous author discovered some case where an existing state value could beat the value in tempState
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't quite understand how is this change correct, what if a new value is set for a key in the prevState? Then it will be ommitted?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the idea is that there can be a race between a new value calling setState()
on withOnyx()
and the time it takes to get this initial value from storage.
And if that happens then we should use the most recent value (the one that was set and not the stale value that we read).
Tbh - not in love with how this was implemented. I would prefer find some way to mark the data as stale if something changed it's value, but I guess we decided the correct place to do that would be in the setState()
callback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, now I got it. Well, this is no longer correct if initialValue
is passed, it will never update because the component starts with a value on that key.
I need to think about how to solve this.
const key = Str.result(mapping.key, props); | ||
let value = Onyx.tryGetCachedValue(key, mapping); | ||
if (!value && mapping.initialValue) { | ||
value = mapping.initialValue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without the hook though - to me, this begs the question of whether the initialValue
should later be restored if the value becomes undefined
in the future. It's an "initial value", but easy to confuse it as a "default value".
Should I still provide a default via props or default props? In this case, yes. It is only used for setting the initial value.
@marcaaron I updated the merging algorithm between prevState and the new state on |
lib/withOnyx.js
Outdated
@@ -19,7 +20,7 @@ function getDisplayName(component) { | |||
return component.displayName || component.name || 'Component'; | |||
} | |||
|
|||
export default function (mapOnyxToState) { | |||
export default function (mapOnyxToState, options = {}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Until there are other options, a simple param is fine.
export default function (mapOnyxToState, options = {}) { | |
export default function (mapOnyxToState, shouldDelayUpdates = false) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I strongly argue against it, boolean params are confusing because they are not labeled. An options object future proofs while keeping things explicit.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
While I don't disagree with the points of your logic, I disagree with the results of that logic. If that logic was followed, then no method anywhere would ever have single params and they would all have one big arguments object.
What about moving it to mapOnyxToState
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Robert C. Martin says it himself:
https://www.informit.com/articles/article.aspx?p=1392524
But Szymon didn't want the key inside mapOnyxToState because that would change how the necessary keys for rendering are discovered. I will change it as a single boolean for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Uncle Bob rules only apply if it's Java code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why would it? it's just code and boolean functions. They get messy over time, there is plenty of arguments against them and I have also lived through long-lived codebases where they make everything hard to understand.
https://medium.com/@amlcurran/clean-code-the-curse-of-a-boolean-parameter-c237a830b7a3
https://understandlegacycode.com/blog/what-is-wrong-with-boolean-parameters/
In any case, not going to push this on you, just sharing my experience.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you please add unit tests to ensure that initialValue
and shouldDelayUpdates
work as expected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for making the change to the boolean param and for the tests.
API.md
Outdated
@@ -131,6 +131,7 @@ Subscribes a react component's state directly to a store key | |||
| [mapping.initWithStoredValues] | <code>Boolean</code> | If set to false, then no data will be prefilled into the component | | |||
| [mapping.waitForCollectionCallback] | <code>Boolean</code> | If set to true, it will return the entire collection to the callback as a single object | | |||
| [mapping.selector] | <code>String</code> \| <code>function</code> | THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data. If the selector is a string, the selector is passed to lodashGet on the sourceData. If the selector is a function, the sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). | | |||
| [mapping.initialValue] | <code>Any</code> | THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be passed as the initial hydrated value for the mapping. It will allow the component to render eagerly while data is being fetched from the DB. Note that it will not cause the component to have the `loading` prop set to true. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it expected that only initialValue
is added to this API doc? It seems like shouldDelayUpdates
and markReadyForHydration
should show up somewhere here. Maybe try re-running the generator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, this document is generated? I modified manually
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I re-ran the generator. However shouldDelayUpdates
and markReadyForHidration
are part of the HOC and not of Onyx, so not sure if I should put them here. That's why I also updated the README
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, OK... I guess that's fine if the generator didn't pick them up. I didn't think about it only being a part of the HOC. I think the stuff you added in the README is good enough for the HOC.
API.md
Outdated
@@ -131,6 +131,7 @@ Subscribes a react component's state directly to a store key | |||
| [mapping.initWithStoredValues] | <code>Boolean</code> | If set to false, then no data will be prefilled into the component | | |||
| [mapping.waitForCollectionCallback] | <code>Boolean</code> | If set to true, it will return the entire collection to the callback as a single object | | |||
| [mapping.selector] | <code>String</code> \| <code>function</code> | THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be used to subscribe to a subset of an Onyx key's data. If the selector is a string, the selector is passed to lodashGet on the sourceData. If the selector is a function, the sourceData and withOnyx state are passed to the selector and should return the simplified data. Using this setting on `withOnyx` can have very positive performance benefits because the component will only re-render when the subset of data changes. Otherwise, any change of data on any property would normally cause the component to re-render (and that can be expensive from a performance standpoint). | | |||
| [mapping.initialValue] | <code>Any</code> | THIS PARAM IS ONLY USED WITH withOnyx(). If included, this will be passed as the initial hydrated value for the mapping. It will allow the component to render eagerly while data is being fetched from the DB. Note that it will not cause the component to have the `loading` prop set to true. | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the initial hydrated value for the mapping
What exactly does "hydrated" mean? 😅
Is there a better term we can use for this that has the same meaning? I can only think of someone drinking a lot of water.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Standard React lingo. But open to suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, yeah, but what does it mean to you? Like, let's say you were explaining this to a 7-year-old.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was talking with another co-worker about this and ChatGPT for the win!
So, I think it would be helpful to change this to say:
If included, this will be passed to the component so that something can be rendered while data is being fetched from the DB. Note that it will not cause the component to have the
loading
prop set to true.
Co-authored-by: Tim Golen <[email protected]>
# Conflicts: # API.md # lib/Onyx.js
PR is updated with latest comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, this is so close! A few conflicts snuck in from the last PR I merged.
# Conflicts: # lib/Onyx.js # lib/storage/providers/IDBKeyVal.js # lib/utils.js
Updated |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One small comment about the mapping.config variable.
if ( | ||
(value !== undefined | ||
&& !Onyx.hasPendingMergeForKey(key)) | ||
|| mapping.allowStaleData |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does allowStaleData
work? I don't see where that will be passed or any documentation added on it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks, like this is the only place where it is used. Seems like it allows the component to be feed undefined data or stale data when there is pending merges. There is no documentation since it is not part of onyx.js
which is the only file the generator looks at. That's why I manually added a section to the README.
const stateUpdate = {...this.tempState}; | ||
delete this.tempState; | ||
|
||
// Full of hacky workarounds to prevent the race condition described above. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NAB / FWIW, I am looking at this code and it looks reasonable since the edge case is well described and this clearly shows the few cases we will run into. Maybe I am a hacker though 😄 Nice work either way.
Looks like a test is failing as well. |
Looks like a missed a change when I did the merge. Tests should pass now. |
@yuwenmemon did you want to give this any final review? |
Details
Margelo is currently with improvements to the ReportScreen speed. After thorough analysis a big bottleneck came from react-native-onyx itself. Components with a lot of connections can cause frequent re-renders or delay the rendering of the component itself until all data has been fetched/hydrated.
We had to implement several changes in order to get the rendering speed down:
initialValue
to a key. Allows to pass a initial value while the data is being fetched, thus allowing the component to mount/render ASAP.delayUpdates
to a withOnyx connection. Allows to delay setting updates/state of thewithOnyx
hook to allow the component to mount first and then update in a single batch at a different time. The child component gets passed aallowUpdates
function that it can use to tell thewithOnyx
HOC to start applying the batched updates.Related Issues
Part of Expensify/App#26087
Automated Tests
Linked PRs