React-native: SectionList Performance iOS

Created on 24 Nov 2017  路  10Comments  路  Source: facebook/react-native

Is this a bug report?

Yes

Have you read the Contributing Guidelines?

Yes

Environment

Environment:
OS: macOS High Sierra 10.13.1
Node: 8.0.0
Yarn: 1.3.2
npm: 5.5.1
Watchman: 4.7.0
Xcode: Xcode 9.1 Build version 9B55
Android Studio: 2.3 AI-162.4069837

Packages: (wanted => installed)
react: ^16.1.1 => 16.1.1
react-native: ^0.50.3 => 0.50.3

Target Platform: iOS (11.1)

Steps to Reproduce

We're having quite a big app completely build with react-native. For navigation we're using react-navigation and for the store redux. As you will see in the following .gif and when you run my example, the performance of our SectionList is really bad. To mention, it's a long list with different sections, sectionheaders and so on, but I can't find the cause of this really bad performance. If I'm testing the app with the PerfMonitor, I'm seeing the UI Thread is dropping frames, the JS thread doesn't have a problem. If I understand things right, then this means that this is not a bug in my JS integration. Is this suggestion right?

(to clarify the performance is not only bad in debug mode, it's also bad on release and on every device I've tested it. (iPad, iPhone 6, iPhone 7))

So steps to reproduce:

  1. Build a large SectionList with different sections
  2. Scroll through the list
  3. Press on items

Expected Behavior

The SectionList should scroll smooth up and down, items can get pressed, just as you would expect it from an app you already know.

Actual Behavior

Really bad performance by scrolling up and down, stocking transitions

Debug Mode with PerfMonitor:
Debug .gif

Release Mode:
Release .gif
(same happens on every test device)

Reproducible Demo

A snack doesn't make really sense in this case, so I uploaded the relevant parts to a new github repository. You can get it here https://github.com/vanBrunneren/Bug.git (obviously some parts are removed because I can't show the full code, but the example should show the problem). To see the problem just download and install my demo, start the app, and scroll through it.

Additional information

Maybe a list of my dependency is also helping:

"dependencies": {
    "bugsnag-react-native": "^2.6.0",
    "prop-types": "^15.5.10",
    "react": "^16.1.1",
    "react-native": "^0.50.3",
    "react-native-camera": "^0.10.0",
    "react-native-carousel": "^0.10.0",
    "react-native-device-info": "^0.11.0",
    "react-native-fabric": "^0.5.1",
    "react-native-facebook-login": "^1.6.0",
    "react-native-google-analytics-bridge": "^5.3.3",
    "react-native-htmlview": "^0.12.1",
    "react-native-maps": "^0.17.1",
    "react-native-push-notification": "^3.0.1",
    "react-native-qrcode-scanner": "^0.0.23",
    "react-native-radio-buttons": "^1.0.0",
    "react-native-render-html": "^3.5.1",
    "react-native-send-intent": "^1.0.21",
    "react-native-swiper": "^1.5.13",
    "react-native-tab-view": "^0.0.70",
    "react-native-vector-icons": "^4.4.2",
    "react-navigation": "^1.0.0-beta.21",
    "react-redux": "^5.0.6",
    "redux": "^3.6.0",
    "redux-logger": "^3.0.6",
    "redux-thunk": "^2.2.0"
  },
  "devDependencies": {
    "babel-jest": "21.2.0",
    "babel-preset-react-native": "4.0.0",
    "jest": "21.2.1",
    "react-test-renderer": "16.0.0"
  },

If I run the same on android it's performing perfectly. The only difference between iOS and android is the navigation structure. On iOS i'm having a TabNavigator and on Android I'm having a DrawerNavigator. But in my opinion this should not be a problem because these problems are always there and are seen without any routing. I made quite the same with a ListView but in fact of replacing ListView through SectionList this performance issues occurred.

If you need additional information or if I can explain something more deeply, let me know.

Locked

Most helpful comment

I have now managed to get a good performance by rethinking and mostly rewriting a lot of my components. It was a huge time consuming process but now I'm having a performant app again. The frustrating thing was, with ListView everything performed very well and as the relaese notes sayed FlatList and SectionList are more performant than the old ListView I had to change several things to get a good performance again. I hope for the future, I don't have to rewrite so much components because of a concept change in react-native.

@bolan9999 your plugin is very well and i would wish react-native would adapt some concepts, but it's always a little bit of a risk with such plugins. Because I'm having a very large app and if sometime your plugin isn't supported anymore or react-native gets changed and there is a huge update to do on your site and then on my site, its always a mess. I had alot of these updates and problems in the past 2 years of developing a very large app in react-native.

I'm closing this issue. For everyone with the same problem:

  • read the docs section "performance"
  • think of how you made your components
  • rethink how you made your components
  • read the docs section "performance" again
  • keep your components simple
  • shouldComponentUpdate is your fried (read exactly how this works and whats the benefit of it)

All 10 comments

Are you implementing your Rows/Headers as PureComponents? What other steps are you doing minimize re-rendering?

@Ehesp I tried several things. I made the Rows PureComponents, I made shouldComponentUpdate lifecycles, I implemented getItemLayout and so on. Nearly everything in the "Performance" Section on the RN docs. Is there a problem with using Redux in the way I'm doing it, because I'm wondering if I'm the only one with such problems.

I made another very small example and I'm still getting performance issues.

HomeView.js

/**
 * 
 */

'use strict';

import React, { Component } from 'react';
import {
    View,
    Text,
    SectionList,
    Image
} from 'react-native';

import {
    fetchJSON,
    updateGlobals
} from '../../actions/dataActions';

import urls from '../../config/urls';

class HomeView extends Component {

    _renderItem({item}) {

        if(item.type) {

            switch(item.type) {

                case "title":
                    return(
                        <View style={{height: 200, justifyContent: 'center', alignItems: 'center'}}>
                            <Text>{item.title}</Text>
                        </View>
                    );
                    break;

                case "more_button":
                    return(
                        <View style={{height: 200, justifyContent: 'center', alignItems: 'center'}}>
                            <Text>{item.title}</Text>
                        </View>
                    );
                    break;

                case "vendor":

                    return (
                        <View style={{height: 200, justifyContent: 'center', alignItems: 'center'}}>
                            <Text>{item.items[0].title}</Text>
                        </View>
                    )
                    break;

                case "deal":

                    return (
                        <View style={{height: 200, justifyContent: 'center', alignItems: 'center'}}>
                            <Text>{item.items[0].title}</Text>
                            <Image
                                source={{uri: 'https://storage.cpstatic.ch'+item.items[0].storageurl_imageTeaser.src}}
                                style={{width: 80, height: 100}} />
                        </View>
                    )
                    break;

            }

        }

        if(item.upoxType) {

            return(
                <View style={{height: 200, justifyContent: 'center', alignItems: 'center'}}>
                    <Text>{item.title}</Text>
                </View>
            );

        }

        return <View />;

    }

    _getItemLayout(data, index) {

        let height = 200;
        return {
            length: height,
            offset: height * index,
            index
        };

    }

    componentDidMount() {

        // Initial Loads ------------------------------------------------------
        this.props.dispatch(fetchJSON('homeView', urls.homeUrl));


        if(this.props.data && this.props.data.navigation && this.props.data.navigation.params && this.props.data.navigation.params.loggedIn) {
            this.props.dispatch(fetchJSON('watchListIds', urls.baseUrl + urls.getWatchList));
        }


        this.props.dispatch(updateGlobals({loggedIn: this.props.data.navigation.params.loggedIn}));
        this.props.dispatch(updateGlobals({activeRoute: 'home'}));
        // ---------------------------------------------------------------------

    }

    render() {

        if(this.props.data && this.props.data.homeView && this.props.data.homeView.sections) {

            return(
                <SectionList
                    getItemLayout={this._getItemLayout}
                    renderItem={this._renderItem.bind(this)}
                    keyExtractor={ (item, index) => index }
                    sections={this.props.data.homeView.sections} />
            );

        }

        return <View />;

    }

}

module.exports = HomeView;

My Sections are coming from a redux state. It's an array with 5 entries, each entry has a data array with one or more items in it.
With this very small example (I'm only showing the title and an Image per row), I'm getting a stocking SectionList as seen in my .gif below.

alt text

Can you share what each item in your data (sections) contains?

alt text

Yeah so the problem is your renderItem needs to return a component which handles shouldComponentUpdate. Currently you're just returns a View inline, which means it'll re-render it every single time (causing you performance issues), as your data is all over the show - it cannot do a shallow compare.

I'd suggest getting the data in a format which is (1) a flat object and (2) in the same format for every single product. Then you can easily handle whether each row should update.

FlatList and SectionList is bad performance. Try this,please. may be it is a surprise for you

https://github.com/bolan9999/react-native-largelist

@bolan9999 no, the performance is bad if you don't implement it properly.

@Ehesp Do not say this sarcastically, I think most developer give up react native because of the FlatList and SectionList. It is the most commonly used component锛宐ut bugs keep 2 years.

I have now managed to get a good performance by rethinking and mostly rewriting a lot of my components. It was a huge time consuming process but now I'm having a performant app again. The frustrating thing was, with ListView everything performed very well and as the relaese notes sayed FlatList and SectionList are more performant than the old ListView I had to change several things to get a good performance again. I hope for the future, I don't have to rewrite so much components because of a concept change in react-native.

@bolan9999 your plugin is very well and i would wish react-native would adapt some concepts, but it's always a little bit of a risk with such plugins. Because I'm having a very large app and if sometime your plugin isn't supported anymore or react-native gets changed and there is a huge update to do on your site and then on my site, its always a mess. I had alot of these updates and problems in the past 2 years of developing a very large app in react-native.

I'm closing this issue. For everyone with the same problem:

  • read the docs section "performance"
  • think of how you made your components
  • rethink how you made your components
  • read the docs section "performance" again
  • keep your components simple
  • shouldComponentUpdate is your fried (read exactly how this works and whats the benefit of it)

If it's a performance problem caused by listview
You can try this library
react-native-nlist

Was this page helpful?
0 / 5 - 0 ratings