Skip to content
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

VirtualizedList CellRenderer optimization #21163

Conversation

christianbach
Copy link

Fixes

Fixes #20174 based of @jasekiw pull request #20208

Changes

  • Remove parentProps from CellRenderer and pass as ordinary props
  • CellRenderer is now a PureComponent

Test Plan:

Tested on a real device with why-did-you-update running to view when the CellRendere component re-rendered. Applied updates to the list items to make sure they still get updated.

Added a test that makes sure the initial onLayout is still called properly.

Release Notes:

[GENERAL] [ENHANCEMENT][VirtualList] - Prevent unnecessary re-renders of the CellRenderer Component.

@facebook-github-bot
Copy link
Contributor

Thank you for your pull request and welcome to our community. We require contributors to sign our Contributor License Agreement, and we don't seem to have you on file. In order for us to review and merge your code, please sign up at https://code.facebook.com/cla. If you are contributing on behalf of someone else (eg your employer), the individual CLA may not be sufficient and your employer may need the corporate CLA signed.

If you have received this in error or have any questions, please contact us at cla@fb.com. Thanks!

Copy link

@analysis-bot analysis-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eslint found some issues. You may run yarn prettier or npm run prettier to fix these.

getItemCount={data => data.length}
/>,
);
const cell = virtualList._cellRefs['i4'];

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dot-notation: ["i4"] is better written in dot notation.

@facebook-github-bot facebook-github-bot added the CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. label Sep 18, 2018
@facebook-github-bot
Copy link
Contributor

Thank you for signing our Contributor License Agreement. We can now accept your code for this (and any) Facebook open source project. Thanks!

@christianbach
Copy link
Author

christianbach commented Sep 20, 2018

@hramos I have tried to ping @sahrens on this PR and the PR #20208 he's probably just super busy like all of us. But perhaps you know someone else thats suited for reviewing PR related to VirtualizedList?

@hramos
Copy link
Contributor

hramos commented Sep 25, 2018

Thanks for adding tests and @matthargett for approving this. I'll import this and put it on the team's review queue, as an additional precaution.

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hramos has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@sahrens
Copy link
Contributor

sahrens commented Sep 28, 2018

Sorry for the delay - thanks for making this change.

Did you test to make sure that sticky headers in RNTester still work?

@christianbach
Copy link
Author

No unfortunately I didn’t. I’ll check and report the results. But I checked all the list files for additional props and didn’t find any other references. Does the sticky headers implemention need CellRenderer to re-render?

@hramos
Copy link
Contributor

hramos commented Sep 28, 2018

When this patch is applied, I get a RedScreen on RNTester, specifically on VirtualizedList, VirtualizedSectionList.js:201: Invariant Violation: Tried to get frame for out of range index NaN

@christianbach
Copy link
Author

Thanks @hramos I just booted up RNTester my self and got the same error. My bad for only testning this on FlatList and SectionLists without Sticky Headers.

@christianbach
Copy link
Author

christianbach commented Sep 28, 2018

@sahrens I'm I correct that the problems lays in the fact that onLayout is extracted by ScrollViewStickyHeader and since it doesn't get the props from cellRenderer we receive a no cellKey and no index?

@christianbach
Copy link
Author

@sahrens @hramos Updated the PR and now RNTester/SectionList works as expected. Thanks for testing!

@christianbach
Copy link
Author

@sahrens @hramos Any updates? I know your super busy, but a update would be appreciated :)

@christianbach
Copy link
Author

@hramos can I do anything to help speed up the review?

Copy link
Contributor

@facebook-github-bot facebook-github-bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hramos has imported this pull request. If you are a Facebook employee, you can view this diff on Phabricator.

@facebook-github-bot facebook-github-bot added the Import Started This pull request has been imported. This does not imply the PR has been approved. label Oct 17, 2018
@hramos
Copy link
Contributor

hramos commented Oct 17, 2018

@christianbach thanks for adding the tests, I'm merging this.

Copy link
Contributor

@matthargett matthargett left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, thanks for adding a unit test!

@christianbach
Copy link
Author

@hramos any updates? 😸

if (child.props.onLayout) {
child.props.onLayout(event);
if (child.props._onLayout) {
child.props._onLayout(event);
Copy link

@sebryu sebryu Nov 12, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are calling component function _onLayout which is not available from child.props.
I think this line should look like this:

Suggested change
child.props._onLayout(event);
child.props.onLayout(event, child.props.cellKey, child.props.index);

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @sebryu and thanks for reviewing this!

_onLayout is available and is part of the rewrite of CellRenderer to stop functions being created on every render

_onLayout = (e): void =>
     this.props.onLayout &&
     this.props.onLayout(e, this.props.cellKey, this.props.index);

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello @christianbach !
I read your code, and I noticed _onLayout function in CellRenderer class. However - you are trying to call it as child.props._onLayout, and simply - _onLayout function is not accessible from child.props. You would need to have ref to CellRenderer to call its functions.

So in this case, my suggested changes should work.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My bad and thanks! I will change it straight away!

Copy link
Author

@christianbach christianbach Nov 14, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally had the time to do a commit. Tested the change in our app without issues.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@sebryu Do I need to do anything else, perhaps rebase from master?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @christianbach !

You have to ping RN guys, I just wanted to create similar issue, so I reviewed your code.
It looks ok for me though 👍

@christianbach
Copy link
Author

@sahrens @hramos I think this is now ready to go

@cihati

This comment has been minimized.

@pull-bot
Copy link

pull-bot commented May 28, 2019

Messages
📖 📋 Missing Summary - Can you add a Summary? To do so, add a "## Summary" section to your PR description. This is a good place to explain the motivation for making this change.
📖 📋 Missing Test Plan - Can you add a Test Plan? To do so, add a "## Test Plan" section to your PR description. A Test Plan lets us know how these changes were tested.
📖

📋 Verify Changelog Format - A changelog entry has the following format: [CATEGORY] [TYPE] - Message.

CATEGORY may be:
  • General
  • iOS
  • Android
  • JavaScript
  • Internal (for changes that do not need to be called out in the release notes)

TYPE may be:

  • Added, for new features.
  • Changed, for changes in existing functionality.
  • Deprecated, for soon-to-be removed features.
  • Removed, for now removed features.
  • Fixed, for any bug fixes.
  • Security, in case of vulnerabilities.

MESSAGE may answer "what and why" on a feature level. Use this to briefly tell React Native users about notable changes.

Generated by 🚫 dangerJS against 14d41f2

@derekstavis
Copy link

derekstavis commented Jun 21, 2019

@christianbach There's a conflict once again. Would you mind fixing it? Also, as soon as the conflict is resolved, can we merge this one or are we missing anything here?

@christianbach
Copy link
Author

@derekstavis sure, will try to find some time this week.

@cpojer
Copy link
Contributor

cpojer commented Jun 28, 2019

The reason we haven't landed this change yet is mainly because nobody at FB has volunteered to ensure that this won't break various surfaces. We have more than 1000 screens written using React Native and this change can potentially break any number of them by not updating things properly and we have no way of automatically detecting it :(

@sunnylqm
Copy link
Contributor

How about make this optimization an option but not enabled by default.

@cpojer
Copy link
Contributor

cpojer commented Jun 28, 2019

If we don't change the default behavior this will be much easier to land, yes!

@azizhk
Copy link

azizhk commented Jul 3, 2019

I tried my hand at not making this the default behavior and accepting an additional prop flattenParentProps, but I couldn't figure out how to retain the current onLayout behavior.

Changes: https://github.com/christianbach/react-native/compare/virtuallist-optimize-cellrenderer...azizhk:virtuallist-optimize-cellrenderer?expand=1

@brunolemos
Copy link
Contributor

@cpojer: We have more than 1000 screens written using React Native and this change can potentially break any number of them by not updating things properly

Can't anyone add extraData={{}} on all FlatLists like Spencer suggested? Should garantee the lists will update and allow merging this pr.

@cihati
Copy link

cihati commented Jul 24, 2019

@christianbach just wanted to say that I admire your tenacity to get this PR merged (after close to a year!), and I am really looking forward to it! Many thanks for putting in the effort.

@azizhk
Copy link

azizhk commented Jul 24, 2019

I was able to contain most of the changes to be opt-in and not be the default behavior.
The diff with @christianbach 's branch is here and with facebook's master branch is here.

Though this has a bit of hacky JS context trickery which might make it brittle. If acceptable, would like some guidance on how to write some good tests so that this does not break easily.

let virtualizedList = this;
this._onCellLayout = function (e) {
  virtualizedList._onCellLayoutGen(e, this.cellKey, this.index)
}

@chaitanyadeorukhkar
Copy link

This PR is beautiful. I hope this gets merged.

CellRenderer re-renders is one of the massive bottlenecks for our performance where we have a VirtualizedList with over 4000 rows.

Thank you everyone who is involved in this PR. Hope I can help here too.

@sunnylqm
Copy link
Contributor

sunnylqm commented Aug 16, 2019

@azizhk If that is too diffcult, how about something like this:

export default (props) => {
   return props.experimentalOptimazation ?  
      <ExperimentalFlatList {...props} /> : <FlatList {...props} />
}

@azizhk
Copy link

azizhk commented Aug 16, 2019

@sunnylqm Thanks. We also have changes in ScrollViewStickyHeader.js so for that we would need to make changes in a lot of Components (like ScrollView). That gave me a different idea, wrapping inside an isExperimentalOptimization Context Provider. Something like:

<VirtualListExperimentalOptimization.Provider>
  <VirtualList {...props} />
</VirtualListExperimentalOptimization.Provider>

This can be passed down to ScrollViewStickyHeader. Let me whip up something over the weekend which agrees to the "not changing the default behavior" requirement.

@brunolemos
Copy link
Contributor

brunolemos commented Aug 18, 2019

@cpojer: If we don't change the default behavior this will be much easier to land, yes!

I have an idea to get this merged:

-const { extraData } = this.props
+const { extraData = {} } = this.props

By changing the default value of extraData to a new object reference, we keep the current FlatList behavior: update on every render.

If the user wants to enable the optimization they just need to pass extraData={null} or some memoized value.

Ideally this would not be necessary, but since internal facebook code is blocking the merge of this pull request this may be a good middle ground solution. No facebook code change necessary while still allowing people to opt in.

@azizhk
Copy link

azizhk commented Aug 20, 2019

I was able to retain the default behavior for onLayout as well: master...azizhk:exp_context_virtuallist

@cpojer Let me know if looks good and I'll create a PR and add tests.

@christianbach
Copy link
Author

Perhaps it's time to close this PR?

@cpojer
Copy link
Contributor

cpojer commented Nov 4, 2019

Alright, I'll close it but I still think it is valueable to make this change in a backwards-compatible way – I'm fine with introducing new props with new names that we can migrate people to.

@cpojer cpojer closed this Nov 4, 2019
@sunnylqm
Copy link
Contributor

sunnylqm commented Nov 5, 2019

@cpojer There is a change in a backwards-compatible way, please review master...azizhk:exp_context_virtuallist
And I dont think @christianbach really means to close the pr but remind you to push this forward.

@azizhk
Copy link

azizhk commented Nov 5, 2019

@cpojer @christianbach I've created my PR: #27115. Hoping we can continue the discussion there. Please close it, if it does not seem viable.

khadorkin pushed a commit to khadorkin/devhub that referenced this pull request Mar 5, 2020
facebook-github-bot pushed a commit that referenced this pull request Apr 20, 2021
Summary:
This sync includes the following changes:
- **[f7cdc8936](facebook/react@f7cdc8936 )**: Also turn off enableSyncDefaultUpdates in RN test renderer ([#21293](facebook/react#21293)) //<Ricky>//
- **[4c9eb2af1](facebook/react@4c9eb2af1 )**: Add dynamic flags to React Native ([#21291](facebook/react#21291)) //<Ricky>//
- **[9eddfbf5a](facebook/react@9eddfbf5a )**: [Fizz] Two More Fixes ([#21288](facebook/react#21288)) //<Sebastian Markbåge>//
- **[11b07597e](facebook/react@11b07597e )**: Fix classes ([#21283](facebook/react#21283)) //<Sebastian Markbåge>//
- **[96d00b9bb](facebook/react@96d00b9bb )**: [Fizz] Random Fixes ([#21277](facebook/react#21277)) //<Sebastian Markbåge>//
- **[81ef53953](facebook/react@81ef53953 )**: Always insert a dummy node with an ID into fallbacks ([#21272](facebook/react#21272)) //<Sebastian Markbåge>//
- **[a4a940d7a](facebook/react@a4a940d7a )**: [Fizz] Add unsupported Portal/Scope components ([#21261](facebook/react#21261)) //<Sebastian Markbåge>//
- **[f4d7a0f1e](facebook/react@f4d7a0f1e )**: Implement useOpaqueIdentifier ([#21260](facebook/react#21260)) //<Sebastian Markbåge>//
- **[dde875dfb](facebook/react@dde875dfb )**: [Fizz] Implement Hooks ([#21257](facebook/react#21257)) //<Sebastian Markbåge>//
- **[a597c2f5d](facebook/react@a597c2f5d )**: [Fizz] Fix reentrancy bug ([#21270](facebook/react#21270)) //<Sebastian Markbåge>//
- **[15e779d92](facebook/react@15e779d92 )**: Reconciler should inject its own version into DevTools hook ([#21268](facebook/react#21268)) //<Brian Vaughn>//
- **[4f76a28c9](facebook/react@4f76a28c9 )**: [Fizz] Implement New Context ([#21255](facebook/react#21255)) //<Sebastian Markbåge>//
- **[82ef450e0](facebook/react@82ef450e0 )**: remove obsolete SharedArrayBuffer ESLint config ([#21259](facebook/react#21259)) //<Henry Q. Dineen>//
- **[dbadfa2c3](facebook/react@dbadfa2c3 )**: [Fizz] Classes Follow Up ([#21253](facebook/react#21253)) //<Sebastian Markbåge>//
- **[686b635b7](facebook/react@686b635b7 )**: Prevent reading canonical property of null ([#21242](facebook/react#21242)) //<Joshua Gross>//
- **[bb88ce95a](facebook/react@bb88ce95a )**: Bugfix: Don't rely on `finishedLanes` for passive effects ([#21233](facebook/react#21233)) //<Andrew Clark>//
- **[343710c92](facebook/react@343710c92 )**: [Fizz] Fragments and Iterable support ([#21228](facebook/react#21228)) //<Sebastian Markbåge>//
- **[933880b45](facebook/react@933880b45 )**: Make time-slicing opt-in ([#21072](facebook/react#21072)) //<Ricky>//
- **[b0407b55f](facebook/react@b0407b55f )**: Support more empty types ([#21225](facebook/react#21225)) //<Sebastian Markbåge>//
- **[39713716a](facebook/react@39713716a )**: Merge isObject branches ([#21226](facebook/react#21226)) //<Sebastian Markbåge>//
- **[8a4a59c72](facebook/react@8a4a59c72 )**: Remove textarea special case from child fiber ([#21222](facebook/react#21222)) //<Sebastian Markbåge>//
- **[dc108b0f5](facebook/react@dc108b0f5 )**: Track which fibers scheduled the current render work ([#15658](facebook/react#15658)) //<Brian Vaughn>//
- **[6ea749170](facebook/react@6ea749170 )**: Fix typo in comment ([#21214](facebook/react#21214)) //<inokawa>//
- **[b38ac13f9](facebook/react@b38ac13f9 )**: DevTools: Add post-commit hook ([#21183](facebook/react#21183)) //<Brian Vaughn>//
- **[b943aeba8](facebook/react@b943aeba8 )**: Fix: Passive effect updates are never sync ([#21215](facebook/react#21215)) //<Andrew Clark>//
- **[d389c54d1](facebook/react@d389c54d1 )**: Offscreen: Use JS stack to track hidden/unhidden subtree state ([#21211](facebook/react#21211)) //<Brian Vaughn>//
- **[c486dc1a4](facebook/react@c486dc1a4 )**: Remove unnecessary processUpdateQueue ([#21199](facebook/react#21199)) //<Sebastian Markbåge>//
- **[cf45a623a](facebook/react@cf45a623a )**: [Fizz] Implement Classes ([#21200](facebook/react#21200)) //<Sebastian Markbåge>//
- **[75c616554](facebook/react@75c616554 )**: Include actual type of `Profiler#id` on type mismatch ([#20306](facebook/react#20306)) //<Sebastian Silbermann>//
- **[1214b302e](facebook/react@1214b302e )**: test: Fix "couldn't locate all inline snapshots" ([#21205](facebook/react#21205)) //<Sebastian Silbermann>//
- **[1a02d2792](facebook/react@1a02d2792 )**: style: delete unused isHost check ([#21203](facebook/react#21203)) //<wangao>//
- **[782f689ca](facebook/react@782f689ca )**: Don't double invoke getDerivedStateFromProps for module pattern ([#21193](facebook/react#21193)) //<Sebastian Markbåge>//
- **[e90c76a65](facebook/react@e90c76a65 )**: Revert "Offscreen: Use JS stack to track hidden/unhidden subtree state" ([#21194](facebook/react#21194)) //<Brian Vaughn>//
- **[1f8583de8](facebook/react@1f8583de8 )**: Offscreen: Use JS stack to track hidden/unhidden subtree state ([#21192](facebook/react#21192)) //<Brian Vaughn>//
- **[ad6e6ec7b](facebook/react@ad6e6ec7b )**: [Fizz] Prepare Recursive Loop for More Types ([#21186](facebook/react#21186)) //<Sebastian Markbåge>//
- **[172e89b4b](facebook/react@172e89b4b )**: Reland Remove redundant initial of isArray ([#21188](facebook/react#21188)) //<Sebastian Markbåge>//
- **[7c1ba2b57](facebook/react@7c1ba2b57 )**: Proposed new Suspense layout effect semantics ([#21079](facebook/react#21079)) //<Brian Vaughn>//
- **[316aa3686](facebook/react@316aa3686 )**: [Scheduler] Fix de-opt caused by out-of-bounds access ([#21147](facebook/react#21147)) //<Andrey Marchenko>//
- **[b4f119cdf](facebook/react@b4f119cdf )**: Revert "Remove redundant initial of isArray ([#21163](facebook/react#21163))" //<Sebastian Markbage>//
- **[c03197063](facebook/react@c03197063 )**: Revert "apply prettier ([#21165](facebook/react#21165))" //<Sebastian Markbage>//
- **[94fd1214d](facebook/react@94fd1214d )**: apply prettier ([#21165](facebook/react#21165)) //<Behnam Mohammadi>//
- **[b130a0f5c](facebook/react@b130a0f5c )**: Remove redundant initial of isArray ([#21163](facebook/react#21163)) //<Behnam Mohammadi>//
- **[2c9fef32d](facebook/react@2c9fef32d )**: Remove redundant initial of hasOwnProperty ([#21134](facebook/react#21134)) //<Behnam Mohammadi>//
- **[1cf9978d8](facebook/react@1cf9978d8 )**: Don't pass internals to callbacks ([#21161](facebook/react#21161)) //<Sebastian Markbåge>//
- **[b9e4c10e9](facebook/react@b9e4c10e9 )**: [Fizz] Implement all the DOM attributes and special cases ([#21153](facebook/react#21153)) //<Sebastian Markbåge>//
- **[f8ef4ff57](facebook/react@f8ef4ff57 )**: Flush discrete passive effects before paint ([#21150](facebook/react#21150)) //<Andrew Clark>//
- **[b48b38af6](facebook/react@b48b38af6 )**: Support nesting of startTransition and flushSync (alt) ([#21149](facebook/react#21149)) //<Sebastian Markbåge>//

Changelog:
[General][Changed] - React Native sync for revisions c9aab1c...f7cdc89

jest_e2e[run_all_tests]

Reviewed By: rickhanlonii

Differential Revision: D27740113

fbshipit-source-id: 6e27204d78e3e16ed205170006cb97c0d6bfa957
lunaleaps pushed a commit to lunaleaps/react-native that referenced this pull request Aug 4, 2021
Summary:
This is VirtualList Optimization done by christianbach in facebook#21163 enabled behind a prop: `experimentalVirtualizedListOpt` with backward compatibility.

Fixes facebook#20174 based of jasekiw pull request facebook#20208

## Changelog

// TODO:

[CATEGORY] [TYPE] - Message

Pull Request resolved: facebook#27115

Test Plan:
// TODO:
Add tests related to backward compatibility. (Will need help)

Differential Revision: D30095387

Pulled By: lunaleaps

fbshipit-source-id: 1c41e9e52beeb79b56b19dfb12d896a2c4c49529
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed This label is managed by the Facebook bot. Authors need to sign the CLA before a PR can be reviewed. Needs: Imported Diff Waiting on Meta
Projects
None yet
Development

Successfully merging this pull request may close these issues.

VirtualizedList- inefficient function passing for CellRenderer