question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Performance] Update `withWindowDimensions` HOC to use a Context Provider internally

See original GitHub issue

If you haven’t already, check out our contributing guidelines for onboarding and email contributors@expensify.com to request to join our Slack channel!


withWindowDimensions can be improved through a refactor that won’t change existing usages and behavior

Currently State:

  • on a chat with 3 messages I have 35 withWindowDimensions HOCs mounted
  • on a chat with 20 messages I have 101 withWindowDimensions HOCs mounted
  • adding an attachment would raise the count by 4-5
  • adding a plain text would raise the count by 2

All of these usages would create a separate class component that:

  • have to be instantiated
  • run it’s lifecycle methods
  • add its own subscription to Dimensions
  • store another instance of the same callback due to binding: this.onDimensionChange = this.onDimensionChange.bind(this);
  • has its own state copy that will be the same with all other withWindowDimensions instances

The main problem with this HOC is not that onDimensionChange is called often - it’s not called at all during init. But initializing a chat with many messages could be choking. On mWeb showing/hiding the keyboard would trigger all the onDimensionsChange listeners, because the viewport do change. This might be something that is causing extra effort combined with the autofocus making the keyboard appear

This functionality can be split in two:

DimensionsContextProvider

A component that will wrap the App the way the SafeAreaProvider does here It will have the class logic currently defined in withWindowDimensions. The difference is it will only mount and subscribe to Dimension events once and will provide any changes to context consumers. When dimensions change the context state will change and all consumers will update the way they currently do. the memory involved doesn’t change as usages grow e.g. O(1) vs O(n)

withWindowDimensions as a DimensionContextConsumer

The HOC will become just a stateless function that passes contextual value to the wrapped component e.g.:

export default function withWindowDimensions(WrappedComponent) {
  const HOC_Wrapper = (props) => (
     <DimensionContext.Consumer>
       {(dimensionProps) => <WrappedComponent {...props} {...dimensionProps} />}
     </DimensionContext.Consumer>
  );

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

Add some throttle or debounce

Wrapping the event handler with throttle or debounce from underscore, should further improve resize performance


This is a branch setup with some logging that you can use to play around: https://github.com/kidroca/Expensify.cash/commit/e2126bc99717970167a44b6fba65a102bc486388

Originally posted here: https://github.com/Expensify/Expensify.cash/issues/2051#issuecomment-816197835

  • this (probably) won’t solve the issue but will be a step in the right direction

Expected Result:

Using the withWindowDimensions should not increase memory usage or subscribe unnecessary listeners

Actual Result:

Each withWindowDimensions usage creates a new component with own lifecycle and own subscription to Dimension events

Action Performed:

N/A

Workaround:

No workaround needed. This is a code organization and performance improvement

Platform:

Where is this issue occurring?

  • Web
  • iOS
  • Android
  • Desktop App
  • Mobile Web

Version Number: 1.0.17-3 Logs: https://stackoverflow.com/c/expensify/questions/4856 Notes/Photos/Videos: Any additional supporting documentation\

https://user-images.githubusercontent.com/12156624/114231381-1068c800-9983-11eb-875c-5e31a2271307.mp4

Expensify/Expensify Issue URL: N/A

Issue Analytics

  • State:closed
  • Created 2 years ago
  • Comments:12 (12 by maintainers)

github_iconTop GitHub Comments

1reaction
kidrocacommented, Apr 21, 2021

At the time I didn’t know a lot about your workflow and that I should present hard evidence of a problem

Looking at the implementation and usages throughout the code I just know it isn’t optimal and provide explanation on that. It’s not just memory, everything is executed (n) times as well - addEventListener, onDimensionChange these will be called simultaneously for all the withWindowDimension class instances. For my sample chats of 15-20 messages there are ~100 instances. Switch between chats would execute these calls again and again. My proposal will bring the class instances to just 1, regardless of message count and the number of withWindowDimension wrappings.

To provide hard evidence and metrics for an issue like that could cost a lot of extra time and efforts. And with no guarantee that I’ll get a job only makes it harder to justify the labor. I try to provide a good explanation and then you can decide is it worth investigating further.

0reactions
marcaaroncommented, Jul 16, 2021

@parasharrajat please feel free to reopen this issue if you have additional metrics to share. Seems like the last time this was discussed we decided it would not be worth it. But maybe you can find something more concrete.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Context - React
Context provides a way to share values like these between components without having to explicitly pass a prop through every level of the...
Read more >
React Context value propagation performance #13739 - GitHub
Hi there! I have observed a performance issue with context providers and value updates. I have spoken with @gaearon with this on twitter, ......
Read more >
Accessing React's Context API Through a Higher Order ...
This article is a step-by-step process on how to set up global state with Context API, and how to use it with a...
Read more >
Performance Optimization in React Context - Disenchanted
Control the Update of Context Value​​ The first problem here is that the context's consumer will be notified with a new value every...
Read more >
React Context API: A deep dive with examples - LogRocket Blog
In this tutorial, we'll explore how we can use React Context to avoid prop drilling. First, we'll cover what prop drilling is and...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found