I found this error :
Can't call setState (or forceUpdate) on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
Context : When I'm connected, I'm on the homepage, this page not contain the breadCrumb, but If I go on CampaignPage
(also the name of the component), I have the BreadCrumb
(Component name) I found this error.
On other post what I could see, they said probably problem on asynchronously on ComponentWillMount
but I think my problem is different and I can't find a solution.
My code look like that :
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import classnames from 'classnames';
import objectAssign from 'object-assign';
import { withRouter } from 'react-router';
import {
BREADCRUMBS_ROUTES,
BREADCRUMBS_ROUTES_FOR_ID,
BREADCRUMBS_ENDPOINT
} from 'constants';
import { getEntityById, setUpdatedBreadcrumbs } from 'actions/breadcrumbs';
import style from './style.scss';
class Breadcrumbs extends Component {
constructor(props) {
super(props);
this.state = {
breadcrumbs: [],
names: {}
};
this.setBreadcrumbs = this.setBreadcrumbs.bind(this);
this.loadEntityNameById = this.loadEntityNameById.bind(this);
}
componentWillMount() {
this.setBreadcrumbs();
}
componentWillReceiveProps(nextProps) {
const { isWillUpdate: newIsWillUpdate } = nextProps;
const { isWillUpdate, saveUpdatedBreadcrumbs } = this.props;
if (isWillUpdate === false && newIsWillUpdate === true) {
this.setBreadcrumbs();
saveUpdatedBreadcrumbs();
}
}
setBreadcrumbs() {
const { params, path } = this.props.match;
let currentPath = '';
const pathSplitedAndExtendet = path.split('/')
.filter(item => !!item)
.map(item => {
if (item[0] === ':' && item.slice(1) !== 'adPage') {
const parameterName = item.slice(1);
this.loadEntityNameById(
parameterName,
params[parameterName]
);
return {
route: `/${params[parameterName]}${BREADCRUMBS_ROUTES_FOR_ID[parameterName]}`,
parameter: parameterName
};
}
return {
route: `/${item}`,
parameter: ''
};
});
const breadcrumbs = pathSplitedAndExtendet
.filter(item => item.parameter !== 'adPage')
.map((item) => {
const indexOfRoute = currentPath.indexOf(item.route);
if (currentPath.slice(indexOfRoute) !== item.route) {
currentPath = `${currentPath}${item.route}`;
}
return ({
...item,
name: BREADCRUMBS_ROUTES[item.route] || '',
route: currentPath
});
});
this.setState({ breadcrumbs });
}
async loadEntityNameById(parameter, id) {
const { loadEntityById } = this.props;
await loadEntityById(BREADCRUMBS_ENDPOINT[parameter], id)
.then((data) => {
this.setState({ names: objectAssign(this.state.names, { [parameter]: { id, name: data.name } }) });
});
}
render() {
const { breadcrumbs, names } = this.state;
const { showBreadcrumbs } = this.props;
return (
<div className={style.breadcrumbs}>
{
showBreadcrumbs && breadcrumbs
.map((item, index) => {
return (
<div
key={`${item.name}--${item.route}--${index}`}
className={classnames(style.bread, index === breadcrumbs.length - 1 ? style.last : null)}
role="link"
tabIndex={-10 - index}
onKeyDown={() => {}}
onClick={item.route ? () => this.props.history.push(item.route) : null}
>
{`${item.name || (names[item.parameter]
? names[item.parameter].name : '...')}
${((breadcrumbs.length > 1) && (index !== breadcrumbs.length - 1)) ? ' >' : ''}
`}
</div>
);
})
}
</div>
);
}
}
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(Breadcrumbs));
You cannot call
setState
incomponentWillMount
try usingcomponentDidMount
insteadThis error message clearly states that your application has memory leak. What's going on here exactly?
Well, you're using async operation (loadEntityNameById) in
setBreadcrumbs
method. Which is being called incomponentWillMount
and incomponentWillReceiveProps
. This means when you go fromCampaignPage
component toBreadCrumb
component, it will do the async operation ie.loadEntityNameById
is running in the background which only sets the state once it's finished. But until that time yourBreadCrumb
component might be unmounted. The react application doesn't allow you to update the state on an unmounted component.Furthermore, you should not use
componentWillMount
method at all. UsecomponentDidMount
hook instead.To fix the issue, what you can do is to set a flag like:
Next, you should clear the didMount property when the component is unmounted.
This will ensure you that your application memory will not be leaked. Because, we properly setting the state when required but not when it doesn't require, and stopping unnecessary loop.
You're doing an asynchronous action (
loadEntityNameById
) that sets the state for the component when it finishes. By that time, yourBreadcrumbs
component may have been unmounted, and the error is thrown.