Search code examples
reactjsreduxreact-reduxreact-dnd

Using shouldComponentUpdate for block component re-render every second


That game developed with react and redux.I am not react-redux developer(I am .net developer) but I must continue that project so I am new in react and redux That game performance is too bad in some android phones.So I analyze project.I see that components render method works every second.My component contain more than 30 other components.So every secon it re render and this is cause bad performance in some old android phones

Why React component re render every second?Can I block this? I search for that problem I see that solution is shouldComponentUpdate function

shouldComponentUpdate(nextProps,nextState) {

        console.log(nextProps.gameStore.get('state'));//waiting
        console.log(this.props.gameStore.get('state'));//waiting

        console.log(this.state);
        console.log(nextState);
        if (nextProps.gameStore.get('state')==this.props.gameStore.get('state')) {
            return false;
        }
        else {
            return true;
        }
        }

but in this function nextstate and this state is same, nextProps.gameStore.get('state') and this.props.gameStore.get('state') is the same.Why next state and current state is same?What should I do?I use constructor but it is still same here is all my component code

    import React from 'react';
import { DragDropContext } from 'react-dnd';
import HTML5Backend from 'react-dnd-html5-backend';
//import { default as HTML5Backend } from 'react-dnd-touch-backend';

import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import { bindFirstArguments } from 'utils/bindFirstArgument';
import * as OkeyGameActions from 'actions/OkeyGameActions';
import * as OkeyNetActions from 'actions/OkeyNetActions';

import * as OkeyChatActions from 'actions/Chatactions';

import { SeatDirection } from 'constants/AppConstants';

import { OkeyScoreboardDialog }
from 'components/OkeyScoreboardDialog/OkeyScoreboardDialog';
import OkeyMatchResultDialog
from 'components/OkeyMatchResultDialog/OkeyMatchResultDialog';

import OkeyRackWrapper from 'components/OkeyRackWrapper/OkeyRackWrapper';
import OkeyTopToolbar from 'components/OkeyTopToolbar/OkeyTopToolbar';
import OkeyTableToolbar from 'components/OkeyTableToolbar/OkeyTableToolbar';
import OkeyTableCenter from 'components/OkeyTableCenter/OkeyTableCenter';

import CustomDragLayer from 'components/CustomDragLayer/CustomDragLayer';
import MessageList from 'components/chat/MessageList';
import PrivateEastMessageList from 'components/chat/PrivateEastMessageList';
import PrivateNorthMessageList from 'components/chat/PrivateNorthMessageList';
import PrivateWestMessageList from 'components/chat/PrivateWestMessageList';
import PrivateSouthMessageList from 'components/chat/PrivateSouthMessageList';

import './_OkeyGame.scss';

function toJS(item) {
  if (item === null) {
    return null;
  }
  //var item1=item.toJS();
  //if (item1.color==='BLACK') {
  //    var a='a';
    //}

  if (item == undefined) {
      return;
  }

  return item.toJS();
}

function getRelativeDirection(selfSeat, direction) {
  let relativeDirection = direction;

  if (selfSeat >= 0) {
    relativeDirection = (selfSeat - direction + 4) % 4;
  }
  return relativeDirection;
}

class OkeyGame extends React.Component {

    constructor(props) {
  super(props);
    }





    shouldComponentUpdate(nextProps,nextState) {

        console.log(nextProps.gameStore.get('state'));//waiting
        console.log(this.props.gameStore.get('state'));//waiting

        console.log(this.state);
        console.log(nextState);
        if (nextProps.gameStore.get('state')==this.props.gameStore.get('state')) {
            return false;
        }
        else {
            return true;
        }
        }
    render() {

    const { dispatch, gameStore, gamePlay, playRules } = this.props;

    let actions = bindActionCreators(OkeyGameActions, dispatch);

    let netActions = bindActionCreators(OkeyNetActions, dispatch);
    const currentTurn = gameStore.get('currentTurn');

    const playState = {
      selectedStone: gamePlay.get('selectedStone'),
      gosterge: gamePlay.get('gosterge'),
      middleStoneCount: gamePlay.get('middleStoneCount'),
      currentTurn: currentTurn
    };


    if (playState.gosterge != undefined) {
        window.localStorage.setItem('gostergeNumber', playState.gosterge._root.entries[0][1]);
        window.localStorage.setItem('gostergeColor', playState.gosterge._root.entries[1][1]);
    }


    const hasOpenedStonesThisTurn = {
      hasOpenedSequenceThisTurn: playRules.get('hasOpenedSequenceThisTurn'),
      hasOpenedPairsThisTurn: playRules.get('hasOpenedPairsThisTurn')
    };

    const rules = {
      canOpenSequence: playRules.get('canOpenSequence'),
      canOpenPairs: playRules.get('canOpenPairs'),
      canWithdraw: playRules.get('canWithdraw'),
      canDiscard: playRules.get('canDiscard'),
      canCollectOpen: playRules.get('canCollectOpen'),
      canLeaveTaken: playRules.get('canLeaveTaken'),
      canProcessStone: playRules.get('canProcessStone')
    };

    const discardMiniBoxes = {
      discardMiniBoxPairs: gamePlay.get('pairs'),
      discardMiniBoxSequence: gamePlay.get('sequence')
    };

    const selfSeat = gameStore.get('selfSeat');

    const { westSeat, eastSeat, northSeat, southSeat } =
    {
      westSeat: getRelativeDirection(selfSeat, SeatDirection.WEST),
      eastSeat: getRelativeDirection(selfSeat, SeatDirection.EAST),
      northSeat: getRelativeDirection(selfSeat, SeatDirection.NORTH),
      southSeat: getRelativeDirection(selfSeat, SeatDirection.SOUTH)
};

    const players = {
      selfSeat: selfSeat,
      pSouth: {
        seatId: southSeat,
        discardStones: gamePlay.getIn(['discardStones', southSeat]),
        profile: toJS(gameStore.getIn(['players', southSeat])),
        dispatch: dispatch
      },
      pNorth: {
        seatId: northSeat,
        discardStones: gamePlay.getIn(['discardStones', northSeat]),
        profile: toJS(gameStore.getIn(['players', northSeat])),
        dispatch: dispatch
      },
      pEast: {
        seatId: eastSeat,
        discardStones: gamePlay.getIn(['discardStones', eastSeat]),
        profile: toJS(gameStore.getIn(['players', eastSeat])),
        dispatch: dispatch
      },
      pWest: {
        seatId: westSeat,
        discardStones: gamePlay.getIn(['discardStones', westSeat]),
        profile: toJS(gameStore.getIn(['players', westSeat])),
        dispatch: dispatch
      }
    };


    let profiles = [
              players.pSouth.profile,
              players.pEast.profile,
              players.pNorth.profile,
              players.pWest.profile
    ];

    localStorage.setItem("selfSeat", selfSeat);
    localStorage.setItem("roomID", gameStore.get('id'));

    if (selfSeat == 0) 
        profiles = [players.pSouth.profile,players.pEast.profile,players.pNorth.profile,players.pWest.profile];

    else if (selfSeat == 1) 
        profiles = [players.pWest.profile,players.pSouth.profile,players.pEast.profile,players.pNorth.profile];

    else if (selfSeat == 2) 
        profiles = [players.pNorth.profile,players.pWest.profile,players.pSouth.profile,players.pEast.profile];

    else if (selfSeat == 3) 
        profiles = [players.pEast.profile,players.pNorth.profile,players.pWest.profile,players.pSouth.profile];


    const matchState = {
      name: gameStore.getIn(['options', 'name']),
      maxRounds: gameStore.getIn(['options', 'rounds']),
      stake: gameStore.getIn(['options', 'stakes']),
      round: gameStore.get('round')
    };



    const owner = gamePlay.get('ownerID');

    const scoreboard = gameStore.get('scoreboard');

    const matchResult = gameStore.get('matchResult');

    const restCountdown = gameStore.get('restCountdown');

    const roomState = gameStore.get('roomState');



const { messageList } = this.props;
const { privateEastMessageList } = this.props;
const { privateNorthMessageList } = this.props;
const { privateWestMessageList } = this.props;
const { privateSouthMessageList } = this.props;

let chatActions = bindActionCreators(OkeyChatActions, dispatch);
// const  dispatch1  = this.props

    // each action has a first argument of room id
netActions = bindFirstArguments(netActions, gameStore.get('id'));

    let from = gameStore.get('from');
    let to = gameStore.get('to');
    let gift = gameStore.get('gift');

    let from1 = gameStore.get('from1');
    let to1 = gameStore.get('to1');
    let gift1 = gameStore.get('gift1');

    let from2 = gameStore.get('from2');
    let to2 = gameStore.get('to2');
    let gift2 = gameStore.get('gift2');

    let from3 = gameStore.get('from3');
    let to3 = gameStore.get('to3');
    let gift3 = gameStore.get('gift3');

    let arayan = gameStore.get('arayan');
    let aranan = gameStore.get('aranan');

    return (
        <div className="game-background" style={{background: 'url(http://okey101.xyz/staticImg/background.png)',backgroundSize:'cover'}}>
      <div className="okey-game flex-centered-column">
       <CustomDragLayer isMini={gamePlay.get('isOver') > 0}></CustomDragLayer>


        <MessageList {...chatActions} {...netActions} messageList={messageList} />

             <OkeyScoreboardDialog profiles={profiles}
      scoreboard={scoreboard} />


        <OkeyMatchResultDialog matchResult={matchResult}
        {...netActions}
        {...actions}
        roomState={roomState}/>


        <OkeyTopToolbar {...netActions}
                        {...matchState}
                        profiles={profiles}/>

        <OkeyTableCenter {...actions}
                         {...netActions}
                         {...playState}
                         {...rules}
                         {...discardMiniBoxes}
                         {...players}
                         owner={owner}
                         messageList={messageList}
                         privateEastMessageList={privateEastMessageList}
                         privateNorthMessageList={privateNorthMessageList}
                         privateWestMessageList={privateWestMessageList}
                         privateSouthMessageList={privateSouthMessageList}
                         from={from} 
                         to={to}
                         gift={gift}
                         from1={from1} 
                         to1={to1}
                         gift1={gift1}
                         from2={from2} 
                         to2={to2}
                         gift2={gift2}
                         from3={from3} 
                         to3={to3}
                         gift3={gift3}
                         arayan={arayan}
                         aranan={aranan}
                         stones={gamePlay.get('stones')}/>

        <OkeyRackWrapper {...actions}
                         {...netActions}
                         {...playState}
                         stones={gamePlay.get('stones')}
                         stoneGroups={gamePlay.get('stoneGroups')}/>
        <OkeyTableToolbar {...actions}
                          {...netActions}
                          {...rules}
                          restCountdown={restCountdown}
                          currentTurn={currentTurn}
                          {...hasOpenedStonesThisTurn}
                          roomState={roomState}
                          stones={gamePlay.get('stones')}
                          {...discardMiniBoxes}
                          okeyStone={gamePlay.get('okeyStone')}/>

      </div>

</div>
    );
  }
}

const mapStateToProps = (state => ({
  gameStore: state.gameStore,
  gamePlay: state.gamePlay,
  playRules: state.playRules,
  messageList: state.MessageList,
  privateEastMessageList: state.PrivateEastMessageList,
  privateNorthMessageList: state.PrivateNorthMessageList,
  privateWestMessageList: state.PrivateWestMessageList,
  privateSouthMessageList: state.PrivateSouthMessageList
}));

const OkeyGameWithDnD = DragDropContext(HTML5Backend)(OkeyGame);

export default connect(mapStateToProps)(OkeyGameWithDnD);

Edit:With Aftab Khan directives I change component to PureComponent but the page does not open and there is not error in console

I change this to

const mapStateToProps = (state => ({
    gameStore: toJS(state.gameStore),
    gamePlay: toJS(state.gamePlay),
    playRules: toJS(state.playRules),
    messageList: toJS(state.MessageList),
    privateEastMessageList: toJS(state.PrivateEastMessageList),
    privateNorthMessageList: toJS(state.PrivateNorthMessageList),
    privateWestMessageList: toJS(state.PrivateWestMessageList),
    privateSouthMessageList: toJS(state.PrivateSouthMessageList)
}));

but it still does not work then I change it to this

const mapStateToProps = (state => ({
    gameStore: state.gameStore.toJS(),
    gamePlay: state.gamePlay.toJS(),
    playRules: state.playRules.toJS(),
    messageList: state.MessageList.toJS(),
    privateEastMessageList: state.PrivateEastMessageList.toJS(),
    privateNorthMessageList: state.PrivateNorthMessageList.toJS(),
    privateWestMessageList: state.PrivateWestMessageList.toJS(),
    privateSouthMessageList: state.PrivateSouthMessageList.toJS()
}));

but it still does not open in browser


Solution

  • There are few things I noticed about your component

    1. render method does all lot of initialization for the children components

    2. shouldComponentUpdate is unnecessary as it deals only with props (which is expected as all your state is in the redux store).

    3. PureComponent is unnecessary to given that you are connecting it to the store using connect(). Your component has not state, its better being and functional components created using a simple es6 arrow function

    For 2. and 3. refer the note on optimizations connect() does for you as it creates containers.

    Now its impossible to tell you why your components are being rendered every second without inspecting every line of your components. However, with carefully consideration of the following can help you make sure re-renders are triggered frugally and only when necessary.

    1. Keep render() method as light-weight as possible render is probably the most called method in all components. More bloated it is slow will you render cycles be. You can move all you localStorage.setItems to componentWillReceiveProps. Each one of those synchronous LocalStorage calls is taking up render time.

    2. Remove shouldComponentUpdate. Instead pass only those props to components that it absolutely requires. Make as many components stateless pure functions

    shouldComponentUpdate is an escape hatch for components that take in more props they need to render. Downside of sCU: it runs before every component re-render. In some cases it can slow down instead of speeding things up. See this comment here by jimfb. This supports my suggestion of turning as many components into stateless function components as you can - including maybe the component you posted here

    3. PureComponent will not be needed if your components are stateless javascript functions taking in only the props they need.