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

Make sure style does not regenerate between renders if it is the same. #3126

Closed
wants to merge 4 commits into from

Conversation

larkox
Copy link

@larkox larkox commented Apr 1, 2022

Description

This PR deals with one performance issue generated by creating a new style object on every render.

Any re-rendering of the parent component may make this component to re-render, and it will generate a brand new style property. This calls the re-rendering from the whole tree below this. This can be specially troublesome with components like VirtualizedLists (which have right now performance problems of their own facebook/react-native#31327 ).

Changes

  • Added a new property to store the last sent style of the component.
  • Added checks to see if the new resulting style has changed from the last style sent.
  • Added another check to make sure any removed variable from the style is also not present in the last sent style.

Test code and steps to reproduce

Flipper can be used to track the re-renders and the reasons. Any simple three level design should be able to reproduce this error. For example:

const Level3 = React.memo(({style}) => (<View style={style} />))
const Level2 = ({style}) => (<Level3 style={style} />)
const AnimatedLevel2 = createAnimatedComponent(View)
const Level1 = () => (<AnimatedLevel2 style={{height:100}} />)

If we re-render the Level1, Level3 should not be re-rendered.

Checklist

  • Included code example that can be used to test this change
  • Updated TS types
  • Added TS types tests
  • Added unit / integration tests
  • Updated documentation
  • Ensured that CI passes

if (!changed) {
const lastKeys = Object.keys(this._lastSentStyle);
const inputKeys = Object.keys(inputStyle);
if (lastKeys.every((k) => inputKeys.includes(k))) {
Copy link
Author

@larkox larkox Apr 1, 2022

Choose a reason for hiding this comment

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

Not sure if it is safe enough to test only for the lengths. The case that bothers me is:

inputStyle = {
  var1: 1,
  var2: 2,
  var3: undefined,
}
lastStyle = {
   var1: 1,
   var2: 2,
   var4: 5,
}

@AlexanderEggers
Copy link
Contributor

@piaskowyk Would you consider this PR for the next release? I think the changes will be quite helpful to improve app performance.

@piaskowyk
Copy link
Member

@AlexanderEggers I will try, but I see here a potential issue with the race condition of the update on the UI thread (mappers). I need to prepare some tests for this PR.

if (!changed) {
const lastKeys = Object.keys(this._lastSentStyle);
const inputKeys = Object.keys(inputStyle);
if (lastKeys.every((k) => inputKeys.includes(k))) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I would at least make it a for of loop with key in inputStyle so we won't have a loop (includes) for every loop iteration (every) for no reason.

Copy link
Author

Choose a reason for hiding this comment

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

I added some code to avoid the double loop.
We have to iterate through the lastStyle, since we already know all relevant information from the input style is present in lastStyle. What we have to make sure is that no extra information is being sent through lastStyle (no extra key in lastStyle that is not present on inputStyle).

I could populate the Set in the previous for, but not sure what is faster (initializing the set directly with an array, or adding one by one the elements).

@derekstavis
Copy link

Just stumbled upon this issue myself. This is generating significant re-renders on my app since I started using Layout Animations in various core components. I am happy to have found this PR so I can at least patch locally :)

@larkox
Copy link
Author

larkox commented Oct 24, 2022

@terrysahaidak Any news here?

@larkox
Copy link
Author

larkox commented Dec 7, 2022

@piaskowyk Any news regarding this PR?

@piaskowyk
Copy link
Member

Hey, I see problem that you don't handle nested properties for example transform: [{}, {}]. Did you test what happens if a render is triggered during animation running?

@larkox
Copy link
Author

larkox commented Jan 20, 2023

After the changes on createAnimatedComponent, the changes on this PR no longer make sense. I am not sure if they are still needed (I think the style is being recreated in _filterNonAnimatedProps, but I would like to check this version working in flipper before trying to fix it).

@larkox larkox closed this Jan 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants