App: [Performance] [Audit] `ReportActionContextMenu` slows down cell rendering time significantly

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”.

Pattern

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.

image

This commit chart spans over 1.4s (c77 to c144). Each spike in the chart is a ReportActionContextMenu rendered for one cell.

Flamegraph

This is an excerpt of the 100th commit (c100):

image

Also note that those spikes could be caused by the heavy usage of react-native-svg, which can impede performance. Quoting William Candillon

Using SVG heavily in an app can degrade its performance, therefore use it with parsimony."

Proposal: render only one context menu, change dynamically its content on press

Instead of rendering ReportActionContextMenu for every cell, render only one menu at the root of the screen. When a report action is pressed, pass the parameters required to update the view to a callback and render the content accordingly. The callback can be shared via a React context.

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Comments: 30 (21 by maintainers)

Most upvoted comments

Opening an issue for this. Thanks for the Information.

Quick update here. We are seeing great progress with the PR opened by Rajat. It should be on staging soon!

Especially on Android where the chats are opening 0.7 seconds faster 🤯

Separately, I think I will look into further improving the metrics for chat switch time so we can track it in Firebase alongside the other critical metrics that have been recently set up.

Big success ! Great job everyone 🙇

Did some A/B testing with context menu logic removed and it looks like we could maybe shave off about ~500ms from every chat switch by refactoring this. Here are some rough numbers (also keep in mind this was in iOS simulator + using the production API)…

Avg. Chat Switch Time w/ Context Menu Avg. Chat Switch Time w/ Context Menu removed Difference
1332.25 ms 901.75 ms 430.5 ms

That seems pretty signifcant since anything over 1 second is going to feel very slow.

@parasharrajat @kidroca Thanks for the extra context. Just a heads up I’m gonna clean these comments up a bit since I think this issue is broader than the withWindowDimensions HOC. If we think the issue over there is worth reopening we can continue conversation here. 🙇

Hey guys this is in line with what I’ve proposed here: https://github.com/Expensify/Expensify.cash/issues/2326

withWindowDimensions doesn’t only add many listeners but uses an instance of a class based component per wrapped component which runs lifecycle methods

You can try to replace the source with one of the samples here: https://github.com/Expensify/Expensify.cash/issues/2326#issuecomment-824181575

src/components/withWindowDimensions.js
import {useWindowDimensions} from 'react-native';

export default function (WrappedComponent) {
    function withWindowDimensions(props) {
        const window = useWindowDimensions();
        const isSmallScreenWidth = window.width <= variables.mobileResponsiveWidthBreakpoint;

        return (
            <WrappedComponent
                windowHeight={window.height}
                windowWidth={window.width}
                isSmallScreenWidth={isSmallScreenWidth}
                {...props}
            />
        );
    }

    withWindowDimensions.displayName = `withWindowDimensions(${getComponentDisplayName(WrappedComponent)})`;
    return withWindowDimensions;
}

But as the comment says the hook implementation of useDimensions would add a new lister per usage so a better approach would be to create a Context provider that uses the hook just once and wraps the app I think I’ve kept the code in a local branch, I can provide a ref This will allow to confirm if there are any benefits to just address withWindowDimensions or the ContextMenu requires more work

@parasharrajat The great advantage of one context to share a state is that it guarantees React fiber will batch every update related to a dimension event in the same commit. Whereas when updating each component because of a DOM event, updates might happen in short bursts of commits which is most likely slower.