Improving Performance Part 2 (#1010)

* Remove serverVersion check to allow GitLab login

* Avoid unncessary re-renders on the root screen

* Avoid unncessary re-renders on the options modal screen

* Avoid unncessary re-renders on the thread screen

* Avoid unncessary re-renders on the offline indicator component

* Avoid unncessary re-renders on the channel loader component

* review feedback
This commit is contained in:
enahum 2017-10-11 14:36:27 -03:00 committed by GitHub
parent dabad3eca6
commit f8414559b2
9 changed files with 59 additions and 99 deletions

View file

@ -6,10 +6,9 @@ import {getTheme} from 'app/selectors/preferences';
import ChannelLoader from './channel_loader';
function mapStateToProps(state, ownProps) {
function mapStateToProps(state) {
const {deviceWidth} = state.device.dimension;
return {
...ownProps,
channelIsLoading: state.views.channel.loading,
deviceWidth,
theme: getTheme(state)

View file

@ -1,37 +1,22 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import {bindActionCreators} from 'redux';
import {connect} from 'react-redux';
import {close as closeWebSocket, init as initWebSocket} from 'mattermost-redux/actions/websocket';
import {getConnection} from 'app/selectors/device';
import OfflineIndicator from './offline_indicator';
function mapStateToProps(state, ownProps) {
function mapStateToProps(state) {
const {websocket} = state.requests.general;
const {appState} = state.entities.general;
const webSocketStatus = websocket.status;
const isConnecting = websocket.error >= 2;
const isConnecting = websocket.error > 1;
return {
appState,
isConnecting,
isOnline: getConnection(state),
webSocketStatus,
...ownProps
webSocketStatus
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators({
closeWebSocket,
initWebSocket
}, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(OfflineIndicator);
export default connect(mapStateToProps)(OfflineIndicator);

View file

@ -1,14 +1,13 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {
ActivityIndicator,
Animated,
Platform,
StyleSheet,
TouchableOpacity,
View
} from 'react-native';
import IonIcon from 'react-native-vector-icons/Ionicons';
@ -24,20 +23,14 @@ const OFFLINE = 'offline';
const CONNECTING = 'connecting';
const CONNECTED = 'connected';
export default class OfflineIndicator extends PureComponent {
export default class OfflineIndicator extends Component {
static propTypes = {
actions: PropTypes.shape({
closeWebSocket: PropTypes.func.isRequired,
initWebSocket: PropTypes.func.isRequired
}).isRequired,
appState: PropTypes.bool,
isConnecting: PropTypes.bool,
isOnline: PropTypes.bool,
webSocketStatus: PropTypes.string
};
static defaultProps: {
appState: true,
isOnline: true
};
@ -53,42 +46,30 @@ export default class OfflineIndicator extends PureComponent {
}
componentWillReceiveProps(nextProps) {
const {appState, isConnecting, isOnline, webSocketStatus} = nextProps;
if (appState) { // The app is in the foreground
if (isOnline) {
if (this.state.network && webSocketStatus === RequestStatus.SUCCESS) {
// Show the connected animation only if we had a previous network status
this.connected();
} else if ((webSocketStatus === RequestStatus.STARTED || webSocketStatus === RequestStatus.FAILURE) && isConnecting) {
// Show the connecting bar if it failed to connect at least twice
this.connecting();
}
} else {
this.offline();
const {webSocketStatus} = this.props;
if (nextProps.isOnline) {
if (this.state.network && webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.SUCCESS) {
// Show the connected animation only if we had a previous network status
this.connected();
} else if (webSocketStatus === RequestStatus.STARTED && nextProps.webSocketStatus === RequestStatus.FAILURE && nextProps.isConnecting) {
// Show the connecting bar if it failed to connect at least twice
this.connecting();
}
} else {
this.offline();
}
}
shouldComponentUpdate(nextProps, nextState) {
return nextState.network !== this.state.network && nextState.network;
}
offline = () => {
this.setState({network: OFFLINE}, () => {
this.show();
});
};
connect = () => {
const {actions, isOnline, webSocketStatus} = this.props;
const {closeWebSocket, initWebSocket} = actions;
initWebSocket(Platform.OS);
// close the WS connection after trying for 5 seconds
setTimeout(() => {
if (webSocketStatus !== RequestStatus.SUCCESS) {
closeWebSocket(true);
this.setState({network: isOnline ? OFFLINE : CONNECTING});
}
}, 5000);
};
connecting = () => {
const prevState = this.state.network;
this.setState({network: CONNECTING}, () => {
@ -111,7 +92,7 @@ export default class OfflineIndicator extends PureComponent {
this.state.top, {
toValue: INITIAL_TOP,
duration: 300,
delay: 1000
delay: 500
}
)
]).start(() => {
@ -146,18 +127,6 @@ export default class OfflineIndicator extends PureComponent {
case OFFLINE:
i18nId = 'mobile.offlineIndicator.offline';
defaultMessage = 'No internet connection';
action = (
<TouchableOpacity
onPress={this.connect}
style={[styles.actionContainer, styles.actionButton]}
>
<IonIcon
color='#FFFFFF'
name='ios-refresh'
size={20}
/>
</TouchableOpacity>
);
break;
case CONNECTING:
i18nId = 'mobile.offlineIndicator.connecting';

View file

@ -7,9 +7,8 @@ import {getDimensions} from 'app/selectors/device';
import OptionsModal from './options_modal';
function mapStateToProps(state, ownProps) {
function mapStateToProps(state) {
return {
...ownProps,
...getDimensions(state)
};
}

View file

@ -10,11 +10,9 @@ import {getTheme} from 'app/selectors/preferences';
import Root from './root';
function mapStateToProps(state, ownProps) {
function mapStateToProps(state) {
return {
...ownProps,
credentials: state.entities.general.credentials,
loginRequest: state.requests.users.login,
theme: getTheme(state)
};
}

View file

@ -1,21 +1,19 @@
// Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {Client, Client4} from 'mattermost-redux/client';
import {RequestStatus} from 'mattermost-redux/constants';
import Loading from 'app/components/loading';
import {stripTrailingSlashes} from 'app/utils/url';
export default class Root extends PureComponent {
export default class Root extends Component {
static propTypes = {
allowOtherServers: PropTypes.bool,
credentials: PropTypes.object,
justInit: PropTypes.bool,
loginRequest: PropTypes.object,
navigator: PropTypes.object,
theme: PropTypes.object,
actions: PropTypes.shape({
@ -23,6 +21,13 @@ export default class Root extends PureComponent {
}).isRequired
};
shouldComponentUpdate(nextProps) {
if (nextProps.credentials !== this.props.credentials) {
return true;
}
return false;
}
componentDidMount() {
if (!this.props.justInit) {
this.loadStoreAndScene();
@ -66,21 +71,15 @@ export default class Root extends PureComponent {
};
loadStoreAndScene = () => {
const {actions, credentials, loginRequest} = this.props;
const {actions, credentials} = this.props;
const {loadMe} = actions;
if (credentials.token && credentials.url) {
// Will probably need to make this optimistic since we
// assume that the stored token is good.
if (loginRequest.status === RequestStatus.NOT_STARTED) {
Client.setToken(credentials.token);
Client4.setToken(credentials.token);
Client4.setUrl(stripTrailingSlashes(credentials.url));
Client.setUrl(stripTrailingSlashes(credentials.url));
Client.setToken(credentials.token);
Client4.setToken(credentials.token);
Client4.setUrl(stripTrailingSlashes(credentials.url));
Client.setUrl(stripTrailingSlashes(credentials.url));
loadMe().then(this.goToLoadTeam).catch(this.goToLoadTeam);
} else {
this.goToLoadTeam();
}
loadMe().then(this.goToLoadTeam).catch(this.goToLoadTeam);
} else {
this.goToSelectServer();
}

View file

@ -14,7 +14,7 @@ import SelectServer from './select_server';
function mapStateToProps(state) {
const {config: configRequest, license: licenseRequest, server: pingRequest} = state.requests.general;
const {config, license, serverVersion} = state.entities.general;
const {config, license} = state.entities.general;
const success = RequestStatus.SUCCESS;
const transition = (pingRequest.status === success && configRequest.status === success && licenseRequest.status === success);
@ -26,7 +26,6 @@ function mapStateToProps(state) {
licenseRequest,
config,
license,
serverVersion,
transition,
theme: getTheme(state)
};

View file

@ -15,7 +15,6 @@ import {
View
} from 'react-native';
import Button from 'react-native-button';
import semver from 'semver';
import {RequestStatus} from 'mattermost-redux/constants';
import {Client, Client4} from 'mattermost-redux/client';
@ -43,7 +42,6 @@ class SelectServer extends PureComponent {
pingRequest: PropTypes.object.isRequired,
configRequest: PropTypes.object.isRequired,
licenseRequest: PropTypes.object.isRequired,
serverVersion: PropTypes.string.isRequired,
actions: PropTypes.shape({
getPing: PropTypes.func.isRequired,
resetPing: PropTypes.func.isRequired,
@ -85,10 +83,9 @@ class SelectServer extends PureComponent {
}
handleLoginOptions = () => {
const {config, intl, license, serverVersion, theme} = this.props;
const version = serverVersion.match(/^[0-9]*.[0-9]*.[0-9]*(-[a-zA-Z0-9.-]*)?/g)[0];
const {config, intl, license, theme} = this.props;
const samlEnabled = config.EnableSaml === 'true' && license.IsLicensed === 'true' && license.SAML === 'true';
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true' && semver.valid(version) && semver.gte(version, 'v3.10.0');
const gitlabEnabled = config.EnableSignUpWithGitLab === 'true';
let options = 0;
if (samlEnabled || gitlabEnabled) {

View file

@ -1,7 +1,7 @@
// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
// See License.txt for license information.
import React, {PureComponent} from 'react';
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import KeyboardLayout from 'app/components/layout/keyboard_layout';
@ -10,7 +10,7 @@ import PostTextbox from 'app/components/post_textbox';
import StatusBar from 'app/components/status_bar';
import {makeStyleSheetFromTheme} from 'app/utils/theme';
export default class Thread extends PureComponent {
export default class Thread extends Component {
static propTypes = {
actions: PropTypes.shape({
selectPost: PropTypes.func.isRequired
@ -25,6 +25,21 @@ export default class Thread extends PureComponent {
state = {};
shouldComponentUpdate(nextProps) {
if (nextProps.posts.length !== this.props.posts.length) {
return true;
}
const length = nextProps.posts.length;
for (let i = 0; i < length; i++) {
if (nextProps.posts[i].id !== this.props.posts[i].id) {
return true;
}
}
return false;
}
componentWillReceiveProps(nextProps) {
if (!this.state.lastViewedAt) {
this.setState({lastViewedAt: nextProps.myMember.last_viewed_at});