I recently built a React component (called ItemIndexItem) which displays images on my application. For example, I have a Search component which will show an index of filtered Items. Each Item displayed is an ItemIndexItem component. Clicking an ItemIndexItem sends you to a ItemShow page where the same ItemIndexItem is employed.
Search.jsx
render() {
return (
<ul>
<li key={item.id}>
<div>
<Link to={`/items/${item.id}`}>
<ItemIndexItem src={item.photos[0].photoUrl} />
<p>${item.price}</p>
</Link>
</div>
</li>
...more li's
</ul>
)
}
ItemIndexItem.jsx
import React, { useState, useEffect } from "react";
export default function ItemIndexItem(props) {
const [imageIsReady, setImageIsReady] = useState(false);
useEffect(() => {
let img = new Image();
img.src = props.src;
img.onload = () => {
setImageIsReady(true);
};
});
if (!imageIsReady) return null;
return (
<div>
<img src={props.src} />
</div>
);
}
The component is working exactly as desired, except for a memory leak error thrown in console:
Can't perform a React state update 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 a useEffect cleanup function.
in ItemIndexItem (created by ItemShow)
in div (created by ItemShow)
For reference, this is the code inside ItemShow where I render the ItemIndexItem:
ItemShow.jsx
return (
...
<div>
<ul>
{this.props.item.photos.map(photo => {
return (
<li key={photo.photoUrl}>
<div>
<ItemIndexItem type='show' src={photo.photoUrl} />
</div>
</li>
);
})}
</ul>
</div>
...
I've tried utilizing a useEffect return function to set img
to null:
return () => img = null;
This does nothing, however. Since I don't create a subscription, there's not one to delete. So I think the problem is in the asynchronous nature of .onload
.