React native performance issue

Viewed 2673

I am using coincap api's to first fetch Data of about 1500+ crypto currency and then Web-socket to update the updated value of crypto Currency.

I a using redux to manage my state here

Inside My componentDidMount(), I am calling a redux action fetchCoin which fetches the value of the coin

componentDidMount() {
    this.props.fetchCoin()
  }

And then In return I am doing something like this

 <FlatList
           data={this.state.searchCoin ? displaySearchCrypto : this.props.cryptoLoaded}
           renderItem={({ item }) => (
           <CoinCard
              key={item["short"]}
              coinShortName = {item["short"]}
              coinName = {item["long"]}
              coinPrice = {item["price"].toFixed(2)}
              percentChange = {item["perc"].toFixed(2)}
              />

Then I have a web-socket which updates the value of cryptocurrency like this

 componentDidUpdate() {
    if (this.state.updateCoinData || this.updateCoinData.length < 1 ) {
      this.updateCoinData = [...this.props.cryptoLoaded];
     this.setState({updateCoinData: true})
    }
      this.socket.on('trades', (tradeMsg) => {
      for (let i=0; i< this.updateCoinData.length; i++) {

        if (this.updateCoinData[i]["short"] == tradeMsg.coin ) {

        //Search for changed Crypto Value
       this.updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
       this.updateCoinData[i]["price"] = tradeMsg['message']['msg']['price']

        //Update the crypto Value state in Redux
        this.props.updateCrypto(this.updateCoinData);

          }
        }
      })
  }

Now, While this work, the problem is that this is slowing my app like hell since whenever the socket sends new data, it has to render every component and hence events like touch and search takes lot of time to execute. [Update] It turns out my app is rendering something even If i remove socket connection, check out update 2

[Question:] What should I do so that I can improve the performance of App? (Something like not using state or using DOM to update my app and so on).

[Update 1:] I am using https://github.com/irohitb/Crypto And these two are js files where all the logic is happening https://github.com/irohitb/Crypto/blob/master/src/container/cryptoContainer.js https://github.com/irohitb/Crypto/blob/master/src/components/CoinCard.js I have also move from map to Flatlist.

[Update: 2] I found that there are endless render happening inside my App which is probably keeping my thread busy (I mean it is endless & unnecessarily passing props). I asked the same question on separate Stackoverflow thread but didn't received a proper response and since it is related to performance, I thought about putting a bounty on it here.

Please check this thread: infinite Render in React

[Answer Update:] While there are many great answers here, Just in case someone wants to understand how it worked, You could probably clone my repository and go back to before this commit. I have linked the commit to the point where my problems was solved (so you might need to go back and see what I was doing wrong). Also, All the answers were very useful and not hard to comprehend so you should definitely go through them.

5 Answers

Each time your component updates it starts a new socket which results in a memory leak and will cause this.props.updateCrypto(updateCoinData); to be called multiple times for the same data. This can be fixed by opening the socket in componentDidMount() and closing it in componentWillUnmount().

You can also buffer multiple record updates and change the FlatList data in one go every couple of seconds.

Edit, working example (App.js):

import React, { Component } from 'react';
import { Text, View, FlatList } from 'react-native';
import SocketIOClient from 'socket.io-client';

type Props = {};
export default class App extends Component<Props> {
    constructor(props) {
        super(props);

        this.currencies = {};
        this.state      = {
            currenciesList: [],
        }
    }

    componentDidMount() {
        this.socket = SocketIOClient('https://coincap.io');

        this.socket.on('trades', (tradeMsg) => {
            const time = new Date();

            // Store updates to currencies in an object
            this.currencies[tradeMsg.message.msg.short] = {
                ...tradeMsg.message.msg,
                time: time.getHours() + ':' + time.getMinutes() + ':' + time.getSeconds(),
            };

            // Create a new array from all currencies
            this.setState({currenciesList: Object.values(this.currencies)})
        });
    }

    componentWillUnmount() {
        this.socket.disconnect();
    }

    render() {
        return (
            <FlatList
                data={this.state.currenciesList}
                extraData={this.state.currenciesList}
                keyExtractor={(item) => item.short}
                renderItem={({item}) => <View style={{flexDirection: 'row', justifyContent: 'space-between'}}>
                    <Text style={{flex: 1}}>{item.time}</Text>
                    <Text style={{flex: 1}}>{item.short}</Text>
                    <Text style={{flex: 1}}>{item.perc}</Text>
                    <Text style={{flex: 1}}>{item.price}</Text>
                </View>}
            />
        );
    }
}

There're many standard ways to improve react app performance, the most common:

  • use usual react optimizations (shouldComponentUpdate, PureComponent - read docs)
  • use virtual lists (limit visible parts of data)

In this case I would add:

Don't process data before optimizations - f.e. formatting data that didn't changed is at least unnecessary. You can insert intermediate component (optimization layer) that will pass/update formatted data into <CoinCard /> only on 'raw data' change.

You might not need Redux at all (store data in state) when data is used in one place/simple structure. Of course you can use redux for other globally shared app state (f.e. filtering options).

Use <FlatList /> (react-native), search for sth more suitable?

UPDATE

Some code was changed in mean time (repo), at this time (08.09) one issue still exist and probably causing memory leaks.

You're calling this.socket.on on each componentDidUpdate call (wrongly coded conditions) - continuously adding a new handler!

componentDidUpdate() {
  // call all ONLY ONCE afer initial data loading
  if (!this.state.updateCoinData && !this.props.cryptoLoaded.length) {
    this.setState({updateCoinData: true}) // block condition
    this.socket.on('trades', (tradeMsg) => {

      // slice() is faster, new array instance
      // let updateCoinData = [...this.props.cryptoLoaded]; 
      let updateCoinData = this.props.cryptoLoaded.slice();

      for (let i=0; i<updateCoinData.length; i++) {
        //Search for changed Crypto Value
        if (updateCoinData[i]["short"] == tradeMsg.coin ) {

          // found, updating from message
          updateCoinData[i]["long"] = tradeMsg["message"]["msg"]["long"]
          updateCoinData[i]["short"] = tradeMsg["message"]["msg"]["short"]
          updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
          updateCoinData[i]["mktcap"] = tradeMsg['message']['msg']["mktcap"]
          updateCoinData[i]["price"] = tradeMsg['message']['msg']['price']

          //Update the crypto Value state in Redux
          this.props.updateCrypto(updateCoinData);

          // record found and updated, no more looping needed
          break;
        }
      }
    })
  }
}

Minor errors: initial fetching states set to true in reducers.

Searching for performance issues I would look at <CoinCard />:

  • make it PureComponent;
  • increased and decreased aren't required to be saved at state which forces unnecessasry render calls;
  • I would use update time (not saved in state, just passed as prop in parent and only for updated rows, within updateCoinData in code above) and derive direction (check for 0 and sign only) of difference (already calculated in perc) only for visible items (from render) and only during time limit (difference between render time and data update prop). setTimeout can be used, too.
  • finally removing componentWillReceiveProps, componentDidUpdate and shouldComponentUpdate should (highly?) improve performance;

Like Bhojendra Rauniyar said, you should use shouldComponentUpdate in CoinCard. You probably also want to change your FlatList, your downsized sample has the FlatList in a ScrollView, this causes the FlatList to fully expand, thus rendering all it's items at once.

class cryptoTicker extends PureComponent {

      componentDidMount() {
        this.socket = openSocket('https://coincap.io');
        this.props.fetchCoin()
        this.props.CurrencyRate()

        this.socket.on('trades', (tradeMsg) => {

            for (let i=0; i< this.updateCoinData.length; i++) {

                if (this.updateCoinData[i]["short"] == tradeMsg.coin ) {

                    //Search for changed Crypto Value
                    this.updateCoinData["short"] = tradeMsg["message"]["msg"]["short"]
                    this.updateCoinData[i]["perc"] = tradeMsg["message"]["msg"]["perc"]
                    this.updateCoinData[i]["price"] = tradeMsg["message"]['msg']['price']

                    //Update the crypto Value state in Redux
                    this.props.updateCrypto(this.updateCoinData);

                }
            }
        })

      }

      componentWillReceiveProps(newProps){
        // Fill with redux data once
        if (this.updateCoinData.length < 1 && newProps.cryptoLoaded) {
            this.updateCoinData = [...newProps.cryptoLoaded];
        }
      }

    render() {

        return (
            <View style={{height: '100%'}}>
                <Header/>
                <FlatList
                    style={{flex:1}}
                    data={this.props.cryptoLoaded}
                    keyExtractor={item => item.short}
                    initialNumToRender={50}
                    windowSize={21}
                    removeClippedSubviews={true}
                    renderItem={({item, index}) => (
                        <CoinCard
                            index={index}
                            {...item}
                        />
                    )}
                />
            </View>
        )
    }
}

class CoinCard extends Component {

    shouldComponentUpdate(nextProps) {
        return this.props.price !== nextProps.price || this.props.perc !== nextProps.perc
    }

    render() {
        console.log("here: " + this.props.index);

        return (
            <View>
                <Text> {this.props.index} = {this.props.long} </Text>
            </View>
        )
    }
}

When rendering Flatlist you should consider using PureComponent or utilizing shouldComponentUpdate hook to update only if required.

From the doc:

If your application renders long lists of data (hundreds or thousands of rows), we recommended using a technique known as “windowing”. This technique only renders a small subset of your rows at any given time, and can dramatically reduce the time it takes to re-render the components as well as the number of DOM nodes created.

Take a deep drive to this performance guide.

If you still want some advanced look through, then I will recommend you to look into the following threads:

FlatList and VirtualizedList Scroll performance is laggy after 30+ rows

Performance problems with react when using a big list

  1. You should never do API calls in React's componentWillMount() lifecycle method, instead it should be done in componentDidMount().

    Check out this very neat article on lifecycle methods and what should be done in which method: https://medium.com/@baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d.

Many will be tempted to use this function in order to send a request to fetch data and expect the data to be available before the initial render is ready. This is not the case — while the request will be initialized before the render, it will not be able to finish before the render is called.

  1. Instead of creating a socket to update the coinData, you might want to make use of redux subscibe/unsubscribe methods.
Related