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
There are few things I noticed about your component
render
method does all lot of initialization for the children components
shouldComponentUpdate
is unnecessary as it deals only with props (which is expected as all your state is in the redux store).
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.