Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React native performance issue

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.

like image 660
iRohitBhatia Avatar asked Aug 25 '18 20:08

iRohitBhatia


2 Answers

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 image 179
xadm Avatar answered Oct 23 '22 00:10

xadm


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>
        )
    }
}
like image 33
basbase Avatar answered Oct 23 '22 00:10

basbase