fix(@embark/core): Prevent unnecessary re-renderings

The services websocket was initiated in the AppContainer and causing all child components to continuously re-render every time there was a service check (which is effectively every second). In addition, the socket was never stopped when not needed (ie when the services component was unmounted).

Create a ServicesContainer that initiates the websocket as part of the container, and stops the socket when the container is unmounted.

Move the ContractsList to be part of the ContractsContainer with a `mode` switch.

Add Deployment page title and description.
This commit is contained in:
emizzle 2019-03-06 15:00:42 +11:00 committed by Iuri Matias
parent cc495c5cab
commit 128ecd4c51
8 changed files with 138 additions and 47 deletions

View File

@ -447,11 +447,13 @@ export const WATCH_SERVICES = 'WATCH_SERVICES';
export const WATCH_NEW_CONTRACT_LOGS = 'WATCH_NEW_CONTRACT_LOGS';
export const WATCH_NEW_CONTRACT_EVENTS = 'WATCH_NEW_CONTRACT_EVENTS';
export const WATCH_CONTRACTS = 'WATCH_CONTRACTS';
export const STOP_CONTRACTS = 'STOP_CONTRACTS';
export const INIT_BLOCK_HEADER = 'INIT_BLOCK_HEADER';
export const STOP_BLOCK_HEADER = 'STOP_BLOCK_HEADER';
export const WATCH_GAS_ORACLE = 'WATCH_GAS_ORACLE';
export const STOP_GAS_ORACLE = 'STOP_GAS_ORACLE';
export const STOP_DEBUGGER = 'STOP_DEBUGGER';
export const STOP_SERVICES = 'STOP_SERVICES';
export function listenToProcessLogs(processName) {
return {
@ -509,12 +511,24 @@ export function listenToContracts(){
};
}
export function stopContracts(){
return {
type: STOP_CONTRACTS
};
}
export function stopGasOracle(){
return {
type: STOP_GAS_ORACLE
};
}
export function stopServices(){
return {
type: STOP_SERVICES
};
}
export function stopDebugger(){
return {
type: STOP_DEBUGGER

View File

@ -18,26 +18,26 @@ function iconClasses(state){
});
}
const Process = ({process}) => (
const Service = ({service}) => (
<Col xs={12} sm={6} md={4} xl={3}>
<Widget02 header={process.name} mainText={process.description} icon={iconClasses(process.state)} color={colorClasses(process.state)} variant="1" />
<Widget02 header={service.name} mainText={service.description} icon={iconClasses(service.state)} color={colorClasses(service.state)} variant="1" />
</Col>
);
Process.propTypes = {
process: PropTypes.object
Service.propTypes = {
service: PropTypes.object
};
const Processes = ({processes}) => (
const Services = ({services}) => (
<Row>
{processes
{services
.sort((a, b) => a.name < b.name ? 1 : 0)
.map((process) => <Process key={process.name} process={process} />)}
.map((service) => <Service key={service.name} service={service} />)}
</Row>
);
Processes.propTypes = {
processes: PropTypes.arrayOf(PropTypes.object)
Services.propTypes = {
services: PropTypes.arrayOf(PropTypes.object)
};
export default Processes;
export default Services;

View File

@ -14,8 +14,6 @@ import {
processes as processesAction,
versions as versionsAction,
plugins as pluginsAction,
listenToServices as listenToServicesAction,
listenToContracts as listenToContractsAction,
initRegularTxs as initRegularTxsAction,
stopRegularTxs as stopRegularTxsAction,
changeTheme, fetchTheme
@ -81,11 +79,8 @@ class AppContainer extends Component {
}
if (this.props.credentials.authenticated && !this.props.initialized) {
this.props.fetchProcesses();
this.props.fetchServices();
this.props.listenToServices();
this.props.fetchPlugins();
this.props.listenToContracts();
this.props.fetchProcesses();
if (enableRegularTxs === "true") {
this.props.initRegularTxs();
this.props.history.replace(stripQueryParam(this.props.location, ENABLE_REGULAR_TXS));
@ -150,9 +145,7 @@ AppContainer.propTypes = {
authenticate: PropTypes.func,
logout: PropTypes.func,
fetchCredentials: PropTypes.func,
initBlockHeader: PropTypes.func,
fetchProcesses: PropTypes.func,
fetchServices: PropTypes.func,
fetchPlugins: PropTypes.func,
fetchVersions: PropTypes.func,
location: PropTypes.object,
@ -160,8 +153,6 @@ AppContainer.propTypes = {
changeTheme: PropTypes.func,
fetchTheme: PropTypes.func,
history: PropTypes.object,
listenToServices: PropTypes.func,
listenToContracts: PropTypes.func,
initRegularTxs: PropTypes.func,
stopRegularTxs: PropTypes.func
};
@ -182,13 +173,10 @@ export default withRouter(connect(
logout: logout.request,
fetchCredentials: fetchCredentials.request,
fetchProcesses: processesAction.request,
fetchServices: processesAction.request,
listenToServices: listenToServicesAction,
fetchVersions: versionsAction.request,
fetchPlugins: pluginsAction.request,
changeTheme: changeTheme.request,
fetchTheme: fetchTheme.request,
listenToContracts: listenToContractsAction,
initRegularTxs: initRegularTxsAction.request,
stopRegularTxs: stopRegularTxsAction.request
},

View File

@ -1,9 +1,14 @@
import React, {Component} from 'react';
import {connect} from 'react-redux';
import PropTypes from 'prop-types';
import {contracts as contractsAction} from "../actions";
import {
listenToContracts as listenToContractsAction,
stopContracts as stopContractsAction,
contracts as contractsAction
} from "../actions";
import Contracts from '../components/Contracts';
import ContractsList from '../components/ContractsList';
import DataWrapper from "../components/DataWrapper";
import PageHead from "../components/PageHead";
import {getContracts} from "../reducers/selectors";
@ -11,15 +16,21 @@ import {getContracts} from "../reducers/selectors";
class ContractsContainer extends Component {
componentDidMount() {
this.props.fetchContracts();
this.props.listenToContracts();
}
componentWillUnmount() {
this.props.stopContracts();
}
render() {
return (
<React.Fragment>
<PageHead title="Contracts" description="Summary of all deployed contracts" />
<DataWrapper shouldRender={this.props.contracts.length > 0} {...this.props} render={({contracts}) => (
<Contracts contracts={contracts} />
)} />
{this.props.updatePageHeader && <PageHead title="Contracts" description="Summary of all deployed contracts" />}
<DataWrapper shouldRender={this.props.contracts.length > 0} {...this.props} render={({contracts}) => {
if (this.props.mode === "list") return <ContractsList contracts={contracts} />;
if (this.props.mode === "detail") return <Contracts contracts={contracts} />;
}} />
</React.Fragment>
);
}
@ -33,13 +44,24 @@ function mapStateToProps(state) {
}
ContractsContainer.propTypes = {
listenToContracts: PropTypes.func,
stopContracts: PropTypes.func,
contracts: PropTypes.array,
fiddleContracts: PropTypes.array,
fetchContracts: PropTypes.func
fetchContracts: PropTypes.func,
mode: PropTypes.string,
updatePageHeader: PropTypes.bool
};
ContractsContainer.defaultProps = {
mode: "detail",
updatePageHeader: true
}
export default connect(
mapStateToProps,{
listenToContracts: listenToContractsAction,
stopContracts: stopContractsAction,
fetchContracts: contractsAction.request
}
)(ContractsContainer);

View File

@ -9,6 +9,7 @@ import {
import ContractsDeployment from '../components/ContractsDeployment';
import DataWrapper from "../components/DataWrapper";
import PageHead from '../components/PageHead';
import {getContracts, getDeploymentPipeline, getWeb3, getWeb3GasEstimates, getWeb3Deployments} from "../reducers/selectors";
class DeploymentContainer extends Component {
@ -18,16 +19,19 @@ class DeploymentContainer extends Component {
render() {
return (
<DataWrapper shouldRender={this.props.contracts.length > 0} {...this.props} render={() => (
<ContractsDeployment contracts={this.props.contracts}
deploymentPipeline={this.props.deploymentPipeline}
web3={this.props.web3}
web3Deploy={this.props.web3Deploy}
web3EstimateGas={this.props.web3EstimateGas}
web3Deployments={this.props.web3Deployments}
web3GasEstimates={this.props.web3GasEstimates}
updateDeploymentPipeline={this.props.updateDeploymentPipeline} />
)} />
<React.Fragment>
<PageHead title="Deployment" description="Deploy your contracts using Embark or a web3-enabled browser such as Mist or MetaMask." />
<DataWrapper shouldRender={this.props.contracts.length > 0} {...this.props} render={() => (
<ContractsDeployment contracts={this.props.contracts}
deploymentPipeline={this.props.deploymentPipeline}
web3={this.props.web3}
web3Deploy={this.props.web3Deploy}
web3EstimateGas={this.props.web3EstimateGas}
web3Deployments={this.props.web3Deployments}
web3GasEstimates={this.props.web3GasEstimates}
updateDeploymentPipeline={this.props.updateDeploymentPipeline} />
)} />
</React.Fragment>
);
}
}

View File

@ -18,12 +18,12 @@ import {
} from "../actions";
import DataWrapper from "../components/DataWrapper";
import Processes from '../components/Processes';
import Console from '../components/Console';
import {EMBARK_PROCESS_NAME, LOG_LIMIT} from '../constants';
import ContractsList from '../components/ContractsList';
import PageHead from '../components/PageHead';
import ServicesContainer from './ServicesContainer';
import {getContracts, getProcesses, getProcessLogs, getServices, getCommandSuggestions} from "../reducers/selectors";
import ContractsContainer from "./ContractsContainer";
class HomeContainer extends Component {
constructor(props) {
@ -53,9 +53,7 @@ class HomeContainer extends Component {
return (
<React.Fragment>
<PageHead title="Dashboard" description="Overview of available services and logs. Interact with Embark using the console. Summary of deployed contracts." />
<DataWrapper shouldRender={this.props.services.length > 0 } {...this.props} render={({services}) => (
<Processes processes={services} />
)} />
<ServicesContainer />
<DataWrapper shouldRender={this.props.processes.length > 0 } {...this.props} render={({processes, postCommand, postCommandSuggestions, processLogs, commandSuggestions}) => (
<Card>
@ -78,7 +76,7 @@ class HomeContainer extends Component {
<CardBody>
<CardTitle>Deployed Contracts</CardTitle>
<div style={{marginBottom: '1.5rem', overflow: 'auto'}}>
<ContractsList contracts={contracts} />
<ContractsContainer contracts={contracts} mode="list" updatePageHeader={false} />
</div>
</CardBody>
</Card>

View File

@ -0,0 +1,49 @@
import PropTypes from "prop-types";
import React, {Component} from 'react';
import {connect} from 'react-redux';
import Services from '../components/Services';
import {
listenToServices as listenToServicesAction,
services as servicesAction,
stopServices as stopServicesAction
} from "../actions";
import DataWrapper from "../components/DataWrapper";
import {getServices} from "../reducers/selectors";
class ServicesContainer extends Component {
componentDidMount() {
this.props.fetchServices();
this.props.listenToServices();
}
componentWillUnmount() {
this.props.stopServices();
}
render() {
return <DataWrapper shouldRender={this.props.services.length > 0 } {...this.props} render={({services}) => (
<Services services={services} />
)} />;
}
}
ServicesContainer.propTypes = {
fetchServices: PropTypes.func,
listenToServices: PropTypes.func,
};
function mapStateToProps(state, _props) {
return {
services: getServices(state)
};
}
export default connect(
mapStateToProps,
{
fetchServices: servicesAction.request,
listenToServices: listenToServicesAction,
stopServices: stopServicesAction
}
)(ServicesContainer);

View File

@ -420,7 +420,15 @@ export function *listenServices() {
const socket = api.webSocketServices(credentials);
const channel = yield call(createChannel, socket);
while (true) {
const services = yield take(channel);
const { cancel, services } = yield race({
services: take(channel),
cancel: take(actions.STOP_SERVICES)
});
if (cancel) {
channel.close();
return;
}
yield put(actions.services.success(services));
}
}
@ -506,7 +514,15 @@ export function *listenContracts() {
const socket = api.webSocketContracts(credentials);
const channel = yield call(createChannel, socket);
while (true) {
const contracts = yield take(channel);
const { cancel, contracts } = yield race({
contracts: take(channel),
cancel: take(actions.STOP_CONTRACTS)
});
if (cancel) {
channel.close();
return;
}
yield put(actions.contracts.success(contracts));
}
}