App: [Performance] [Audit] `withOnyx(ReportActionCompose)` is very slow to render
If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!
This report is part of #3957, scenario “Rendering Individual chat messages”.
Commit log
The full commit log can be inspected with Flipper or React Devtools, see https://github.com/Expensify/Expensify.cash/issues/3957#issuecomment-881045715 for instructions. This excerpt takes commits relevant to one withOnyx(ReportActionCompose) instance, child of cell with key 70797025.
SHOW LOG
<dl> <dh>19</dh> <dd>RenderswithOnyx(ReportActionCompose), first render</dd>
<dh>24</dh>
<dd>Renders withOnyx(ReportActionCompose), because betas state changed</dd>
<dh>25</dh>
<dd>Renders withOnyx(ReportActionCompose), because comment state changed</dd>
<dh>26</dh>
<dd>Renders withOnyx(ReportActionCompose), because modal state changed</dd>
<dh>27</dh>
<dd>Renders withOnyx(ReportActionCompose), because network state changed</dd>
<dh>28</dh>
<dd>Renders withOnyx(ReportActionCompose), because myPersonalDetails state changed</dd>
<dh>29</dh>
<dd>Renders withOnyx(ReportActionCompose), because personalDetails state changed</dd>
<dh>30</dh>
<dd>Renders withOnyx(ReportActionCompose), because reportActions state changed</dd>
<dh>31</dh>
<dd>Renders withOnyx(ReportActionCompose), because report state changed</dd>
<dh>76</dh>
<dd>Renders withOnyx(ReportActionCompose), because report state changed</dd>
<dh>192</dh>
<dd>Renders withOnyx(ReportActionCompose), because blockedFromConcierge state changed</dd>
<dh>193</dh>
<dd>Renders withOnyx(ReportActionCompose), because loading state changed</dd>
<dh>215</dh>
<dd>Renders withOnyx(ReportActionCompose), because reportActions state changed</dd>
<dh>220</dh>
<dd>Renders withOnyx(ReportActionCompose), because report state changed</dd>
</dl>
Flamegraph

Problems
First of all, the commit log already highlights issue reported in #4101, but there is more. Inspect the log by pressing SHOW LOG in the section above.
Issue 1: loaded state is set to true very lately (c220, at 6.1s)
It looks like blockedFromConcierge is fired lately (c192, at 5.4s) while the latest preceding value was fired in c76 (at 1.7s), so that’s over 3 seconds later.
Issue 2: report and reportActions are emitted multiple times
reportis emitted at c31, c76 and c220reportActionsis emitted at c30 and c215
Proposal: investigate further those Onyx bottlenecks
- Assess why
blockedFromConciergeis fired 3.7 seconds after the first burst ending at c31 - Assess why
reportandreportActionsare emitted multiple times for the same cell
About this issue
- Original URL
- State: closed
- Created 3 years ago
- Comments: 21 (16 by maintainers)
I’m not sure what are you observing
maxToRenderPerBatch={1}. I’ve observed this effect, where an initial count if items render and then +1 item gets rendered each 0.5-1s. This is actually costing more CPU time as noted here: #2629This is how React works by design, it would batch some updates together for performance. You can see it in my data as well, where several renders of the same component type would have a different start time, but the same commit time (commit is when didMount is called)
withOnyx(ReportActionItem)
Another reason for such delay is the optimization we have for retrieving value from storage
If the components above all need something that’s not in cache - the first component that needs it starts a read from storage, any other components needing the same value would just hook to that call and wait otherwise they will make their own request to read it in parallel and just make it worse for everyone The component that rendered for 137ms probably started mounting about 400ms later than the first one and so it waits the least amount of time. Keeping more items in cache should help with this
Also it’s very likely you’re printing live on the console
withOnyx.js:123which slows things considerably if it happens for everywithOnyxcomponent (2000+ renders during init)Hey guys I think I know why this view is slow to render and it is this composition right here
https://github.com/Expensify/Expensify.cash/blob/429e7bb7e4cd6bcb945375102c780ee9b600c7f2/src/pages/home/report/ReportActionCompose.js#L445-L458
This composition is evaluated at render time, before adding the spinner you could even see the compose box coming together in chunks
A quick test can be to remove the
AtthacmentModaland picker and just render theTextInputFocusable- I did it a long time ago (before emojis and the other items added there) and it rendered instantly@marcaaron
The thing is, we’re only interested in one peculiar report action in this component. There is a high chance that the report action didn’t change after being fetched, but the reference in memory will have changed, causing a re-render. Is there a history version ID for each report action (or last modified field) that you could use in order to avoid extraneous commits, by using
shouldComponentUpdateorReact.memo? That logic could otherwise be handled in Onyx to avoid re-emitting report actions that haven’t changed.