On my site I have 3 categories. When I click on one of them (for example “sneaker”), I want to display only the products in the selected category. I’m currently doing it this way:
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import baseURL from "../../utils/baseURL";
import { fecthProductsAction } from "../../redux/slices/products/productsSlice";
import LoadingComponent from "../../components/LoadingComp/LoadingComponent";
import ErrorMsg from "../../components/ErrorMsg/ErrorMsg";
import Products from "../../components/Users/Products/Products";
import { useSearchParams } from "react-router-dom";
import {
Accordion,
AccordionDetails,
AccordionSummary,
Container,
Grid,
} from "@mui/material";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import { fetchBrandsAction } from "../../redux/slices/brands/brandsSlice";
export default function PLP() {
const [color, setColor] = useState("");
const [price, setPrice] = useState("");
const [selectedBrands, setSelectedBrands] = useState([]);
const [page, setPage] = useState(1);
const [loadingData, setLoadingData] = useState(false);
const [params] = useSearchParams();
const category = params.get("category");
const dispatch = useDispatch();
// Carrega marcas ao carregar a página
useEffect(() => {
dispatch(fetchBrandsAction());
}, [dispatch]);
const { products, error } = useSelector((state) => state?.products);
const { brands } = useSelector((state) => state?.brands);
const brandsData = brands?.data?.slice(3, 9);
// Função para buscar produtos
const fetchProducts = () => {
setLoadingData(true);
const brandString = selectedBrands.join(",");
const productUrl = `${baseURL}/products?category=${category || ""}&brand=${
brandString || ""
}&color=${color || ""}&price=${price || ""}&page=${page}&limit=4`;
try {
dispatch(fecthProductsAction({ url: productUrl }));
} catch (error) {
console.error("Erro na requisição de produtos:", error);
}
setLoadingData(false);
};
// Carrega produtos ao alterar filtros ou página
useEffect(() => {
if (category || selectedBrands.length || color || price) {
fetchProducts();
}
}, [category, selectedBrands, color, price, page, dispatch]);
// Atualiza a lista de produtos
const [productData, setProductData] = useState([]);
useEffect(() => {
if (products) {
const uniqueNewProducts =
products.data?.filter(
(newProduct) => !productData.some((p) => p.id === newProduct.id)
) || [];
setProductData((prevData) =>
page === 1 ? uniqueNewProducts : [...prevData, ...uniqueNewProducts]
);
}
}, [products, page]);
console.log("dados finai", productData);
// Reinicia a página ao mudar qualquer filtro de busca
useEffect(() => {
setPage(1);
setProductData([]); // Limpa a lista de produtos quando qualquer filtro muda
}, [category, selectedBrands, color, price]);
// Função para gerenciar o scroll infinito
useEffect(() => {
const handleScroll = () => {
if (
window.innerHeight + document.documentElement.scrollTop >=
document.documentElement.offsetHeight - 100
) {
// Incrementa a página ao atingir o final da página
if (!loadingData) {
setPage((prevPage) => prevPage + 1);
}
}
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, [loadingData]);
return (
<Container fixed>
<Grid container spacing={2} direction="row" sx={{ marginY: "5rem" }}>
{/* Filtros de Marca */}
<Grid item xs={12} sm={12} md={3} lg={3} sx={{ height: "auto" }}>
<Accordion defaultExpanded>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-controls="panel1-content"
id="panel1-header"
>
Marca
</AccordionSummary>
<AccordionDetails>
<div className="space-y-2">
{brandsData?.map((brandItem) => (
<div key={brandItem?._id} className="flex items-center">
<input
type="checkbox"
className="h-4 w-4 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500"
checked={selectedBrands.includes(brandItem?.name)}
onChange={() =>
setSelectedBrands((prevSelected) =>
prevSelected.includes(brandItem?.name)
? prevSelected.filter((b) => b !== brandItem?.name)
: [...prevSelected, brandItem?.name]
)
}
/>
<label className="ml-3 min-w-0 flex-1 text-gray-500">
{brandItem?.name}
</label>
</div>
))}
</div>
</AccordionDetails>
</Accordion>
</Grid>
{/* Produtos */}
<Grid
container
item
xs={12}
sm={12}
md={9}
lg={9}
sx={{
height: products ? "auto" : "100vh",
}}
>
<Products products={productData} />
{loadingData && <LoadingComponent />}
{error && <ErrorMsg message={error?.message} />}
</Grid>
</Grid>
</Container>
);
}
That means, I get the URL param for the category with
const category = params.get("category");
And loading the data with useEffect based on the URL inside the funcion fecthProducts(). The thing is, when I click on a category, the items are rendered continuously, and in the end I get a list of all the products from the call on page 1, and not just the items in that selected category.
Can anyone help me?
I’m using Redux, and this is the slice:
import {
createAsyncThunk,
createSlice,
rejectWithValue,
} from "@reduxjs/toolkit";
import axios from "axios";
import baseURL from "../../../utils/baseURL";
//initial state
const initialState = {
loading: false,
error: null,
product: {},
products: [],
isAdded: false,
isUpdated: false,
isDeleted: false,
};
//create product action
export const addProductAction = createAsyncThunk(
"products/create",
async (payload, { rejectWithValue, getState, dispatch }) => {
try {
const {
name,
description,
category,
sizes,
brand,
colors,
price,
totalQty,
files,
} = payload;
//http request
//token
const token = getState().users?.userAuth?.userInfo?.token;
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
//formData
const formData = new FormData();
formData.append("name", name);
formData.append("brand", brand);
formData.append("category", category);
formData.append("description", description);
formData.append("totalQty", totalQty);
formData.append("price", price);
sizes?.forEach((size) => {
formData.append("sizes", size);
});
colors?.forEach((color) => {
formData.append("color", color);
});
files?.forEach((file) => {
formData.append("files", file);
});
const { data } = await axios.post(
`${baseURL}/products`,
formData,
config
);
return data;
} catch (error) {
return rejectWithValue(error?.response?.data);
}
}
);
//create product action
export const updateProductAction = createAsyncThunk(
"products/update",
async (payload, { rejectWithValue, getState, dispatch }) => {
try {
const {
id,
name,
description,
category,
sizes,
brand,
colors,
price,
totalQty,
//files,
} = payload;
//http request
//token
const token = getState().users?.userAuth?.userInfo?.token;
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
// //formData
// const formData = new FormData();
// formData.append("name", name);
// formData.append("brand", brand);
// formData.append("category", category);
// formData.append("description", description);
// formData.append("totalQty", totalQty);
// formData.append("price", price);
// sizes?.forEach((size) => {
// formData.append("sizes", size);
// });
// colors?.forEach((color) => {
// formData.append("color", color);
// });
// files?.forEach((file) => {
// formData.append("files", file);
// });
const { data } = await axios.put(
`${baseURL}/products/update/${id}`,
{ name, description, category, sizes, brand, colors, price, totalQty },
config
);
return data;
} catch (error) {
return rejectWithValue(error?.response?.data);
}
}
);
//fetch all products
export const fecthProductsAction = createAsyncThunk(
"products/fetch-all",
async ({ url }, { rejectWithValue }) => {
try {
const { data } = await axios.get(`${url}`);
console.log("Dados retornados da API:", data); // Adicione este log
return data;
} catch (error) {
console.log(error);
return rejectWithValue(error?.response?.data);
}
}
);
//fetch a single product
export const fetchProductAtion = createAsyncThunk(
"product/details",
async (id, { rejectWithValue }) => {
try {
const { data } = await axios.get(`${baseURL}/products/${id}`);
return data;
} catch (error) {
return rejectWithValue(error?.response?.data);
}
}
);
//delete a single product
export const deleteProductAtion = createAsyncThunk(
"product/delete",
async (id, { rejectWithValue, getState }) => {
//token
const token = getState().users?.userAuth?.userInfo?.token;
const config = {
headers: {
Authorization: `Bearer ${token}`,
},
};
try {
const { data } = await axios.delete(
`${baseURL}/products/delete/${id}`,
config
);
return data;
} catch (error) {
return rejectWithValue(error?.response?.data);
}
}
);
// Action para resetar o estado de isAdded
export const resetProductAdded = createAsyncThunk("products/resetAdded", () => {
return false;
});
//products slice
const productsSlice = createSlice({
name: "products",
initialState,
extraReducers: (builder) => {
//create product
builder.addCase(addProductAction.pending, (state) => {
state.loading = true;
});
builder.addCase(addProductAction.fulfilled, (state, action) => {
state.loading = false;
state.isAdded = true;
state.product = action.payload;
});
builder.addCase(addProductAction.rejected, (state, action) => {
state.error = action.payload;
state.loading = false;
state.product = null;
state.isAdded = false;
});
// Reset isAdded
builder.addCase(resetProductAdded.fulfilled, (state) => {
state.isAdded = false;
});
//update product
builder.addCase(updateProductAction.pending, (state) => {
state.loading = true;
});
builder.addCase(updateProductAction.fulfilled, (state, action) => {
state.loading = false;
state.isUpdated = true;
state.product = action.payload;
});
builder.addCase(updateProductAction.rejected, (state, action) => {
state.error = action.payload;
state.loading = false;
state.product = null;
state.isUpdated = false;
});
//fetch all
builder.addCase(fecthProductsAction.pending, (state) => {
state.loading = true;
});
builder.addCase(fecthProductsAction.fulfilled, (state, action) => {
state.loading = false;
state.products = action.payload;
});
builder.addCase(fecthProductsAction.rejected, (state, action) => {
state.loading = false;
state.products = null;
state.error = action.payload;
});
//fetch single
builder.addCase(fetchProductAtion.pending, (state) => {
state.loading = true;
});
builder.addCase(fetchProductAtion.fulfilled, (state, action) => {
state.loading = false;
state.product = action.payload;
});
builder.addCase(fetchProductAtion.rejected, (state, action) => {
state.loading = false;
state.product = action.payload;
state.product = null;
});
//delete single
builder.addCase(deleteProductAtion.pending, (state) => {
state.loading = true;
});
builder.addCase(deleteProductAtion.fulfilled, (state, action) => {
state.loading = false;
state.product = action.payload;
state.isDeleted = true;
});
builder.addCase(deleteProductAtion.rejected, (state, action) => {
state.loading = false;
state.error = action.payload;
state.product = null;
state.isDeleted = false;
});
},
});
//generate reducer
const productsReducer = productsSlice.reducer;
export default productsReducer;
There is no problem with the API. It works fine.
The full code is here, in case you want to point out the adjustment: https://github.com/SoaresAnjos/garimpaae
I expect to recieve just the correspondent items when I click on a category