I want to display a list of products based on specific categories fetched from api, like below:
const API = "https://dummyjson.com/products";
const ProductsList = () => {
const { cate } = useParams(); //here I am getting category from Viewall component
const { getFilterProducts, filter_products } = useFilterContext();
useEffect(() => {
getFilterProducts(`${API}/category/${cate}`);
}, [cate]);
return (
<div className="mx-2 mt-2 mb-16 md:mb-0 grid grid-cols-1 md:grid-cols-12">
<div className="h-9 w-full md:col-span-2">
<FilterSection />
</div>
<div className="md:col-span-10">
<ProductListDetails products={filter_products} />
</div>
</div>
);
};
My FilterContextProvider is as follows
const initialState = {
filter_products: [],
};
const FilterProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
const { products } = useAppContext();
const getFilterProducts = async (url) => {
dispatch({ type: "FILTERS_LOADING" });
try {
const res = await fetch(url);
const data = await res.json();
if (!res.ok) {
var error = new Error("Error" res.status res.statusText);
throw error;
}
dispatch({ type: "LOAD_FILTER_PRODUCTS", payload: data.products });
} catch (err) {
dispatch({ type: "FILTERS_ERROR", payload: err.message });
}
};
return (
<FilterContext.Provider value={{ ...state, getFilterProducts }}>
{children}
</FilterContext.Provider>
);
};
I tried using this simple approach in my ProductList
component to clean up:
useEffect(() => {
let inView = true;
getFilterProducts(`${API}/category/${cate}`);
return () => {
inView = false;
};
}, [cate]);
But it does not seem to work. When I move to the ProductList
component, it first displays data of my previous filer_products
value, then after a few fractions of seconds, updates the data and shows current data.
I am expecting that when the ProductList
component unmounts, its rendered data should vanish, and when I navigate it again, it should render the current data directly, not after a fraction of seconds.
CodePudding user response:
As you explained, I assume your context is wrapping your routes, and it's not re-rendering when switching between pages. A simple solution is to have a loader in ProductsList
, wait for the new data to replace the old, and have the user notice what's happening with a loader:
const ProductsList = () => {
const { cate } = useParams(); //here I am getting category from Viewall component
const { getFilterProducts, filter_products } = useFilterContext();
const [loading, setLoading] = useState(true);
useEffect(() => {
getFilterProducts(`${API}/category/${cate}`).then(() => {
setLoading(false);
});
}, [cate]);
if (loading) {
return <p>Hang tight, the data is being fetched...</p>;
}
return (
<div className="mx-2 mt-2 mb-16 md:mb-0 grid grid-cols-1 md:grid-cols-12">
<div className="h-9 w-full md:col-span-2">
<FilterSection />
</div>
<div className="md:col-span-10">
<ProductListDetails products={filter_products} />
</div>
</div>
);
};
If you need to clear your store in a clean-up function, you can add dispatch
as part of your context value, grab it in ProductsList
and call it like so:
<FilterContext.Provider value={{ ...state, getFilterProducts, dispatch }}>
{children}
</FilterContext.Provider>
const { getFilterProducts, filter_products, dispatch } = useFilterContext();
useEffect(() => {
getFilterProducts(`${API}/category/${cate}`);
return () => {
dispatch({ type: "LOAD_FILTER_PRODUCTS", payload: {} });
};
}, [cate]);