[Performance] Update `withWindowDimensions` HOC to use a Context Provider internally
See original GitHub issueIf 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\
Expensify/Expensify Issue URL: N/A
Issue Analytics
- State:
- Created 2 years ago
- Comments:12 (12 by maintainers)
Top GitHub Comments
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 thewithWindowDimension
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 ofwithWindowDimension
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.
@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.