In my App, I am implementing Product Listing page which lists out the products on the listing page. I am looking to integrate some good practices based on this post – https://dev.to/vyan/using-async-functions-in-useeffect-best-practices-and-pitfalls-o75 but instead of let
I am using ref
to track.
I am getting the list of products in the console message but it displays no list in the UI. When I comment the – isMounted.current = false;
line in my cleanup function then only it displays the list. Let me know what I am doing wrong here with the cleanup ?
App.jsx –
import { useEffect, useRef, useState } from "react";
import ProductsList from "./components/ProductsList";
import "./styles.css";
export default function App() {
const isMounted = useRef(true);
const [products, setProducts] = useState([]);
const fetchData = async () => {
try {
const data = await fetch("https://dummyjson.com/products");
const { products = [] } = await data.json();
console.log(products); // It shown as Array of object in the console
if (isMounted.current) {
setProducts(products);
}
} catch (error) {
if (isMounted.current) {
console.error("Error fetching data: ", error);
}
}
};
useEffect(() => {
fetchData();
return () => {
isMounted.current = false; // Cleanup on unmount
};
}, []);
return (
<div className="App">
<h1>Product Listing Page</h1>
{products.length ? <ProductsList products={products} /> : null}
</div>
);
}
ProductsList.jsx –
const ProductsList = ({ products }) => {
return (
<div className="products-list">
{products.map((product) => {
return (
<div className="product" key={product.id}>
<div className="max-w-sm rounded overflow-hidden shadow-lg">
<img
className="w-full"
src={product.thumbnail}
alt={product.thumbnail}
/>
<div className="px-6 py-4">
<div className="font-bold text-xl mb-2">{product.title}</div>
<p className="text-gray-700 text-base">{product.description}</p>
</div>
<div className="px-6 pt-4 pb-2">
{product.tags.map((tag, index) => (
<span
key={index}
className="inline-block bg-gray-200 rounded-full px-3 py-1 text-sm font-semibold text-gray-700 mr-2 mb-2"
>
#{tag}
</span>
))}
</div>
</div>
</div>
);
})}
</div>
);
};
export default ProductsList;
2
Answers
In React Strict Mode, React invokes your effect setup and cleanup function (simulating a mount/umount) twice. Because of this, when the setup function is reinvoked for the second time, the ref value that was updated in the cleanup function would still be set to
false
(see this discussion on whether this is intended behaviour).The React team discourage using refs to detect mounts and also suggests using variables as mentioned in your article to determine if the component has unmounted.
If you must use refs, you can reset your ref to
true
on the initial mount (essentially "undoing" the effects from the strict-mode cleanup call):However, I’d suggest you try with the variable approach as suggest in the docs to avoid this pattern.
To Further explain the answer above, In React’s Strict Mode the setup and cleanup functions of effects are invoked twice on mount and unmount to help identify side effects that might not clean up properly. So this means that when you use a ref to track whether the component is mounted or not, the ref can be set to false during cleanup. If your effect runs again (due to the double invocation), the ref will still be false, which could prevent your data-fetching logic from updating the state.
Using a Variable Approach
Instead of relying on a ref to track whether a component is mounted, you can use a simple variable within the effect. Here’s how you can implement this: