In a lot of my components I need to do something like this:
handleSubmit() {
this.setState({loading: true})
someAsyncFunc()
.then(() => {
return this.props.onSuccess()
})
.finally(() => this.setState({loading: false}))
}
The onSuccess
function
loading
should stay true until it is resolved)If the function unmounts the component, this.setState({loading: false})
obviously triggers a warning Can't call setState (or forceUpdate) on an unmounted component.
My 2 questions:
_isMounted
variable in componentDidMount
and componentWillUnmount
and then check it when needed in most of my components, plus I may forget to do it next time writing something like this ...it indicates a memory leak in my application
, but it is not a memory leak in this case, is it ? Maybe ignoring the warning would be ok ...EDIT: The second question is a little bit more important for me than the first. If this really is a problem and I just can't call setState
on unmounted component, I'd probably find some workaround myself. But I am curious if I can't just ignore it.
Live example of the problem:
const someAsyncFunc = () => new Promise(resolve => {
setTimeout(() => {
console.log("someAsyncFunc resolving");
resolve("done");
}, 2000);
});
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {loading: false};
}
componentDidMount() {
setTimeout(() => this.handleSubmit(), 100);
}
handleSubmit() {
this.setState({loading: true})
someAsyncFunc()
/*
.then(() => {
return this.props.onSuccess()
})
*/
.finally(() => this.setState({loading: false}))
}
render() {
return <div>{String(this.state.loading)}</div>;
}
}
class Wrapper extends React.Component {
constructor(props, ...rest) {
super(props, ...rest);
this.state = {
children: props.children
};
}
componentDidMount() {
setTimeout(() => {
console.log("removing");
this.setState({children: []});
}, 1500)
}
render() {
return <div>{this.state.children}</div>;
}
}
ReactDOM.render(
<Wrapper>
<Example />
</Wrapper>,
document.getElementById("root")
);
.as-console-wrapper {
max-height: 100% !important;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
Unfortunately you have to keep track of "isMounted" yourself.
To simplify you control flow you could use async/await
:
handleSubmit() {
this.setState({loading: true})
try {
await someAsyncFunction()
await this.props.onSuccess()
} finally {
if (this._isMounted) {
this.setState({loading: false})
}
}
}
This is actually mentioned in the react docs, which points to this solution: https://gist.github.com/bvaughn/982ab689a41097237f6e9860db7ca8d6
If your someAsyncFunction
supports cancelation, you should do so in componentWillUnmount, as encouraged by this article. But then - of course - check the return value and eventually not call this.props.onSuccess
.
class myClass extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
data: [],
};
}
componentDidMount() {
this._isMounted = true;
this._getData();
}
componentWillUnmount() {
this._isMounted = false;
}
_getData() {
axios.get('example.com').then(data => {
if (this._isMounted) {
this.setState({ data })
}
});
}
render() {
...
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With