I've a component named "Item" which creates and calls a promise when it has been mounted.
class Item extends React.Component{
constructor(props){
super(props)
this.onClick = this.onClick.bind(this)
this.prom = new Promise((resolve, reject) => {
setTimeout(() => resolve("PROMISE COMPLETED "+this.props.id),6000)
})
}
componentDidMount(){
this.prom.then((success) => {
console.log(success)
})
}
componentWillUnmount(){
console.log("unmounted")
}
onClick(e){
e.preventDefault()
this.props.remove(this.props.id)
}
render(){
return (
<h1>Item {this.props.id} - <a href="#" onClick={this.onClick}>Remove</a></h1>
)
}
}
As you can see, the promise calls the resolve 6 seconds after it has been called.
There is another component named "List" that is responsible for showing those items on the screen. The "List" is the parent of the "Item" component.
class List extends React.Component{
constructor(props){
super(props)
this.state = {
items : [1,2,3]
}
this.handleRemove = this.handleRemove.bind(this)
}
handleRemove(id){
this.setState((prevState, props) => ({
items : prevState.items.filter((cId) => cId != id)
}));
}
render(){
return (
<div>
{this.state.items.map((item) => (
<Item key={item} id={item} remove={this.handleRemove} />
))
}
</div>
)
}
}
ReactDOM.render(<List />,root)
On the example above, it shows three Item on the screen.
If I remove any of those components, componentWillUnmount() is called but also the promise which has been created in the removed component is run.
For example, I can see the promise of the second item is run even if I remove the second item.
unmounted
PROMISE COMPLETED 1
PROMISE COMPLETED 2
PROMISE COMPLETED 3
I have to cancel the promise when a component is unmounted.
A variation of this https://hshno.de/BJ46Xb_r7 seemed to work for me.
I made an HOC with the mounted
instance variable and wrapped all async components in it.
Below is what my code roughly loks like.
export function makeMountAware(Component) {
return class MountAwareComponent extends React.Component {
mounted = false;
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
return (
<Component
mounted = {this.mounted}
{...this.props}
{...this.state}
/>
);
}
}
class AsyncComponent extends React.Component {
componentDidMount() {
fetchAsyncData()
.then(data => {
this.props.mounted && this.setState(prevState => ({
...prevState,
data
}));
});
}
}
export default makeMountAware(AsyncComponent);