Reputation: 1
I'm beginner in react/redux and I came across a weird behaviour of my application every try of dispatching action e.g.
store.dispatch({type: 'no_matter_what_is_here'});
remounting all components even if the state of store doesn't changed over and over and provides infinite rendering of component (component using 'connect' function from 'react-redux' library).
I'm using these libraries:
"dependencies": {
"babel-polyfill": "^6.3.14",
"bluebird": "^3.4.1",
"eventsource-polyfill": "^0.9.6",
"font-awesome-webpack": "0.0.4",
"history": "^4.7.2",
"lodash": "^4.17.4",
"material-ui": "^0.19.1",
"moment": "^2.13.0",
"prop-types": "^15.5.10",
"react": "^15.6.1",
"react-dom": "^15.6.1",
"react-dropzone": "^3.5.1",
"react-modal": "^1.4.0",
"react-redux": "^5.0.2",
"react-router": "^3.0.0",
"react-router-dom": "^4.2.2",
"react-router-redux": "^4.0.6",
"react-scripts": "1.0.13",
"react-tap-event-plugin": "^2.0.1",
"redux": "^3.6.0",
"superagent": "^3.1.0",
"uuid": "^3.0.1"
},
What is the cause of this behaviour?
Code of example component(but it's concern every components in application)
import React, { Component } from 'react';
import store from '../../store';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import _ from 'lodash';
import { CircularProgress } from 'material-ui';
import debounce from '../../modules/debounce';
import { JobAddDialog } from '../job';
import WorkdeckPanel from './workdeckPanel';
import QueuedJobList from './queuedJobList';
import { Label, WarningDialog, InfoDialog } from '../controls';
import { setupActions, jobActions } from '../../actions';
import {
connectTask,
reConnectTask,
cancelTask,
loadStatus,
startTask,
pauseTask,
stopTask,
skipTask,
retryTask,
} from '../../actions/printerActions';
import { getEnumName } from '../../modules/enumHelpers';
import { printerErrorTypeEnum, printerStatusEnum } from '../../constants';
class Status extends Component {
static get propTypes() {
return {
printer: PropTypes.shape({
status: PropTypes.number.isRequired,
}),
jobs: PropTypes.shape({
list: PropTypes.array.isRequired,
}).isRequired,
resources: PropTypes.shape({}).isRequired,
};
}
static get defaultProps() {
return {
printer: {},
jobs: {
list: [],
},
};
}
constructor(props) {
super(props);
this.state = {
showAddDialog: false,
showConfirm: false,
showStop: false,
selectedJobId: null,
};
this.onJobSelected = this.onJobSelected.bind(this);
this.onStatusLoaded = this.onStatusLoaded.bind(this);
}
componentWillMount() {
store.dispatch({type: 'no_matter_what'});
}
componentDidUpdate() {
const { printer } = this.props;
const { showAddDialog } = this.state;
const { isLoading, status } = (printer || {});
if (!isLoading && !showAddDialog
&& [printerStatusEnum.notResponding, printerStatusEnum.confirming, printerStatusEnum.suspended].indexOf(status) === -1) {
debounce(this.onStatusLoaded, 1000);
}
}
onStatusLoaded() {
const { jobs, printer } = this.props;
loadStatus(printer.updateDate)
.then((res) => {
const job = Object.assign({ id: -1 }, printer.job);
const newJob = res.job ? _.find(jobs.list, { id: res.job.id }) : { id: -1 };
if (newJob.id !== job.id) {
return jobActions.loadJobs();
}
return null;
});
}
onJobSelected(selectedJobId) {
this.setState({ selectedJobId });
}
render() {
const { jobs, printer, resources } = this.props;
const { selectedJobId, showAddDialog, showConfirm, showStop } = this.state;
return (
<div className="statusContainer">
<QueuedJobList {...{
jobs, selectedJobId, onJobSelect: this.onJobSelected, onJobAdd: () => { this.setState({ showAddDialog: true }); },
}}
/>
<WorkdeckPanel {...{ jobs,
printer,
selectedJobId,
resources,
onStartClick: () => {
if (printer.job && printer.status === printerStatusEnum.suspended) {
this.setState({ showConfirm: true });
} else {
startTask();
}
},
onStopClick: () => { this.setState({ showStop: true }); },
}}
/>
{ [printerStatusEnum.initializing].indexOf(printer.status) !== -1
? (<InfoDialog {...{
title: resources.get('device.connecting.title'),
show: true,
onCancel: cancelTask }}
>
<div className="iconProgress"><CircularProgress thickness={5} /></div>
<h3 className="textAlert">
<Label path="device.connecting.note" />
</h3>
</InfoDialog>)
: null }
<JobAddDialog {...{
show: showAddDialog,
isQueued: true,
onClose: () => { this.setState({ showAddDialog: false }); },
}}
/>
<ConfirmDialog {...{ printer, showConfirm, onHide: () => { this.setState({ showConfirm: false }); }, resources }} />
<NotRespondingDialog {...this.props} />
<ErrorDialog {...{ showStop, onCancel: () => { this.setState({ showStop: true }); }, printer, resources }} />
<StopDialog {...{ show: showStop, onClose: () => { this.setState({ showStop: false }); }, resources }} />
</div>
);
}
}
const ConfirmDialog = ({ printer, showConfirm, onHide, resources }) => {
const { status, method } = printer;
let onCancel = onHide;
let show = showConfirm;
if (status === printerStatusEnum.confirming) {
onCancel = () => {
onHide();
cancelTask();
};
show = true;
}
if (show) {
return (
<InfoDialog {...{
title: resources.get('device.confirming.title'),
okCaption: resources.get('buttons.continue'),
show,
onCancel,
onOk: () => {
onHide();
startTask();
},
}}
>
<Label {...{ path: 'device.confirming.note', replacements: method }} />
</InfoDialog>);
}
return null;
};
const NotRespondingDialog = (props) => {
const { resources, printer } = props;
if (printer.status === printerStatusEnum.notResponding) {
return (
<WarningDialog {...{
title: resources.get('device.notResponding.title'),
okCaption: resources.get('buttons.retry'),
show: true,
buttons: [
{ type: 'Cancel', onClick: cancelTask },
{ type: 'Retry', onClick: reConnectTask, isPrimary: true },
] }}
>
<Label path="device.notResponding.note" />
</WarningDialog>);
}
return null;
};
const ErrorDialog = ({ showStop, onCancel, printer, resources }) => {
const { status, errorType } = printer;
if (status === printerStatusEnum.inError && !showStop) {
const error = getEnumName(printerErrorTypeEnum, errorType);
let buttons = [
{ type: 'Ok', onClick: pauseTask, isPrimary: true },
];
if (errorType === printerErrorTypeEnum.tubeError) {
buttons = [
{ type: 'Cancel', onClick: onCancel },
{ type: 'Skip', onClick: skipTask },
{ type: 'Retry', onClick: retryTask, isPrimary: true },
];
}
return (
<WarningDialog {...{
title: resources.get(`device.${error}.title`),
show: true,
buttons }}
>
<Label {...{ path: `device.${error}.note` }} />
</WarningDialog>);
}
return null;
};
const StopDialog = ({ show, onClose, resources }) => {
if (show) {
return (
<WarningDialog {...{
title: resources.get('device.stopping.title'),
show: true,
buttons: [
{ type: 'Cancel', onClick: onClose },
{ type: 'Ok', onClick: () => { stopTask().then(() => { onClose(); }); }, isPrimary: true },
] }}
>
<Label className="textInfo" path="device.stopping.note" />
</WarningDialog>);
}
return null;
};
export default connect(
state => ({
jobs: state.jobs,
printer: state.printer,
resources: state.resources,
}),
)(Status);
Upvotes: 0
Views: 856
Reputation: 2170
Your mistake likely in componentDidUpdate
method, you need to console each step all values in conditions to catch permament reconcollation:
Upvotes: 0
Reputation: 780
First of all you should split this code into Presentational Componentes and Containers. Containers keep the logic and the connect with your store. This would make your code less error prone and easier to read.
Regarding your issue you are make this this dispatch store.dispatch({type: 'no_matter_what'});
on componentWillMount
. It isn't a good practise as you can read here. I'd recommend you to remove it from there.
Also, I'd look into those bindings you have there. Try to understand if you really need them as they are atm. I don't have enough knowledge but I'd look into this article which is pretty nice (not perfect though).
In this case, I suggest you to use arrow functions and make sure you need this onStatusLoaded
binding. You are binding it in the constructor and inside onStatusLoaded
you seem to be updating status again every time your component updates, which will cause a cycle.
Upvotes: 1