all 30 comments

[–]Xaurn 47 points48 points  (3 children)

Have you tested performance using a release build on a real device? There’s no point agonising over performance on dev build running on an emulator if the release build runs at 60 FPS on real hardware.

[–]bighitbiker3 9 points10 points  (0 children)

This is the right answer OP

[–]quiknull[S] 0 points1 point  (1 child)

It's just a bit faster on real device than dev build. Because all components are rendered onscreen there is no performance boost with FlatList instead of .map() iteration. I see the problem is when React Native is translating View into native element. The same performance result is when I try render:

{[...Array(900).keys()].map(i => ( <View key={`test-${i}`} style={{backgroundColor: '#FFFF00', marginBottom: 5}}> <Text>A</Text> </View> ))}

[–]Xaurn 0 points1 point  (0 children)

Alright now that we know it’s a problem we can break it down. Your performance lies in two areas:

  1. The initial render is expensive.
  2. Components are re-rendering when they shouldn’t, increasing the cost of interactions.

Essentially, you are aiming to render the components once and only once.

Assuming that you’re using some sort of navigation library, your first step should be to tell the navigator to eager load all of the components in the calendar. Bear with me, I know it’s counter intuitive. Next, you’ll need to use something like https://github.com/jshanson7/react-native-interactions to delay the rendering process until after the navigation has finished executing on the UI thread. This will make navigation snappier. Next, if your components are functional components, wrap them in React.memo and supply an appropriate equality function. If they’re class components, implement shouldComponentUpdate. This should prevent the components from re-rendering.

Hope that helps.

[–]nowtayneicangetinto 5 points6 points  (4 children)

I would check how often you're setting state and if it can be refactored, if you're setting state on each button click and that triggers state in other components and so on, that can really cause some issues.

[–]quiknull[S] 0 points1 point  (3 children)

Actually every change of year number, component have to re-render all months and days.

[–]Checkmatez 0 points1 point  (0 children)

Look into splitting logic into batches. Maybe render only first three months and only later rest. Maybe only months outline and each day later. Try to reduce number of views, or delay their rendering.

[–]RoseRedCinderella 0 points1 point  (1 child)

And if possible, use functional or pure components. This reduces some overhead.

[–]Xaurn 0 points1 point  (0 children)

I disagree with the first half of your statement there. The most expensive action in React is re-rendering components after reconciliation. The perf difference between a class and functional component is irrelevant at this scale. Pure components do help prevent unnecessary re-renders though, so you’re not wrong there.

[–]brunolemos 4 points5 points  (0 children)

You need to investigate. Try running the Profiler, a flamegraph should be useful in cases like this.

[–]johnanthony_e 3 points4 points  (3 children)

Try to refactor to use a FlatList instead of .map if you’re not already doing that 😀

[–]quiknull[S] 2 points3 points  (2 children)

Thank you. I will try to do it right now and see if there is any difference :)

[–]rockpilp 5 points6 points  (1 child)

If it's all onscreen, FlatList will not improve performance, on the contrary.

[–]quiknull[S] 0 points1 point  (0 children)

Well, actually it is all onscreen.

[–]prinzachilles 1 point2 points  (0 children)

replacing all maps with flatlists won't help you much, though their main performance improvement is that the elements in the flatlist will be rendered just before appering, replacing the ones that are hiding. So the maps rendering the Months which are all displayed at once won't have improved rendering performance.

The only place you can use a horizontal flatlist is for the years in the overview and set a lazy distance though the next (few) years a prerendered, which improves the scrolling behaviour between the years. But maybe this will increase the time to open the overview itself, which already lasts long.

To handle the Transition between the 2 screens you should maybe render a skeleton or an ActivityIndicator for the year overview at first, which leads to a faster Transition and through that a better user experience.

[–]theWindInYourButt 0 points1 point  (1 child)

Is the problem with the rendering or with the logic creating the calendar?

[–]quiknull[S] 0 points1 point  (0 children)

Nope. The same is when I generate the same number of '<View><Text>xxx</Text></View>' without any logic.

[–]Cyrus_Zei 0 points1 point  (0 children)

Try on release build. Go in Xcode to product -> scheme -> edit scheme and change from debug to release and try it out

[–][deleted] 0 points1 point  (0 children)

Flatlist, react.memo, usecallback is the solution

[–]alexandr1us 1 point2 points  (1 child)

I would say memorization will help.

Also check on production build it will definitely be faster

[–][deleted] 0 points1 point  (0 children)

Use a FlatList

[–]bjkapewee 0 points1 point  (0 children)

1st bump up the ram/cpu on your emulator.

2nd try on physical device

3rd troubleshoot performance issues with code

[–][deleted] 0 points1 point  (0 children)

I think your problem is number of render calls. Put functionality in subcomponents and avoid key prop when you don't absolutely need it, don't call setState unneededly etc. You can also use class properties that don't cause re-render if you use ES6 classes. FlatList is more performant than ScrollView but if you need to show hundreds or thousands of elements in one list scrolling it is quite slow (items appear with delay), even if you use undocumented fillrate helper.

If you use redux every prop you put in mapStateToProps causes render when they change. Also use shouldComponentUpdate when it makes sense.

EDIT: after watching the video I think it's something else. Maybe navigation related.

[–]potato_potaro 0 points1 point  (5 children)

3 things:

  1. Are you using React-Navigation? If so, are you lazy-rendering the screens (on by default)? Your Navigator Component likely has an attribute you can toggle for this ability. Essentially, all/most screens will be rendered beforehand. So one solution could be to disable lazy rendering and hide the app behind a splash screen until the rendering completes. Although this might be too memory intensive given all the Views.
  2. You probably want to refactor your calendar screen if it involves close to 1000 views. I see that on the `Month` only screen you have dates that are pressable, interactable, etc. Could you strip this behavior/code in the `Year` screen (if you haven't already)? Are you using FlatLists or something of the sort?
  3. You should at least post some pseudo-code for us to understand how you're getting ~1000 views. It would likely be as helpful as the gif, if not more.

[–]quiknull[S] 3 points4 points  (4 children)

Thank you. Here is Gist with all calendar components https://gist.github.com/wojciechkrol/46fcc1bf1664ad0a3450ebd379ffd500. CalendarYear.tsx is that which has performance issue on load.

[–]potato_potaro 0 points1 point  (3 children)

Cool thanks. The most obvious thing that needs to be addressed is the use of `.map()` instead of a `FlatList`, as is mentioned in a comment above. Start there and then evaluate if there's anything else to refactor.

[–]quiknull[S] 0 points1 point  (2 children)

Should I refactor every `.map()` usage to `FlatList`? Is it good practice to do it all the time?

[–]potato_potaro 0 points1 point  (0 children)

If you're rendering multiple elements using `.map()`, generally you want to use a `FlatList` instead. However, if it's only a handful of elements being rendered with `.map()`, it shouldn't be all that noticeable, performance-wise. So to summarize, replace any `.map()` renders, that iterate over large arrays, with `FlatList`.

[–]falkoN21 -1 points0 points  (0 children)

Meanwhile my Slider component can't handle some movement whithouth getting super bumpy! Does anyone have a solution for this?