I'm building an E-commerce app with React and I stumbled on a problem that React doesn't render the UI based on the initial state when first start the page.
sort state which has the initial state of "latest", based on this Sorting functionality - if sort has a value of "latest" - it will sort and return the newest items first.sort current state. On start, the sort value is already "latest".product.js file, I already sorted the items with mongoose by the field createdAt - 1 but seems like it doesn't apply on the UI?-> What would be the case here that makes React not render the items based on initial state and how can we fix it?
ProductList.jsx
const ProductList = () => {
const location = useLocation()
const category = location.pathname.split("/")[2]
const [filters, setFilters] = useState({});
const [sort, setSort] = useState("latest");
// For Filters Bar
const handleFilters = (e) => {
const value = e.target.value
// When choosing default value, show all products:
if (value === "") {
return setFilters([])
} else {
setFilters({
...filters,
[e.target.name]: value,
})
}
}
return (
<Container>
<Navbar />
<Announcement />
<Title>Dresses</Title>
<FilterContainer>
<Filter>
<FilterText>Filter Products: </FilterText>
<Select name="color" onChange={handleFilters}>
<Option value="">All Color</Option>
<Option value="white">White</Option>
<Option value="black">Black</Option>
<Option value="brown">Brown</Option>
<Option value="red">Red</Option>
<Option value="blue">Blue</Option>
<Option value="yellow">Yellow</Option>
<Option value="green">Green</Option>
</Select>
<Select name="size" onChange={handleFilters}>
<Option value="">All Size</Option>
<Option>XS</Option>
<Option>S</Option>
<Option>M</Option>
<Option>L</Option>
<Option>XL</Option>
<Option>36</Option>
<Option>37</Option>
<Option>38</Option>
<Option>39</Option>
<Option>40</Option>
<Option>41</Option>
<Option>42</Option>
<Option>43</Option>
</Select>
</Filter>
<Filter>
<FilterText>Sort Products: </FilterText>
<Select onChange={e => setSort(e.target.value)}>
<Option value="latest">Latest</Option>
<Option value="oldest">Oldest</Option>
<Option value="asc">Price ↑ (Low to High)</Option>
<Option value="desc">Price ↓ (High to Low)</Option>
</Select>
</Filter>
</FilterContainer>
<Products category={category} filters={filters} sort={sort} />
<Newsletter />
<Footer />
</Container>
);
}
Products.jsx
const Products = ({ category, filters, sort }) => {
const [products, setProducts] = useState([])
const [filteredProducts, setFilteredProducts] = useState([])
useEffect(() => {
const getProducts = async () => {
try {
const res = await axios.get( category
? `http://localhost:5000/api/products?category=${category}`
: `http://localhost:5000/api/products`
)
setProducts(res.data)
} catch (err) {
console.log(`Fetch all items failed - ${err}`)
}
}
getProducts()
}, [category])
useEffect(() => {
category && setFilteredProducts(
products.filter(item =>
Object.entries(filters).every(([key, value]) =>
item[key].includes(value)
)
)
)
}, [category, filters, products])
// Sorting:
useEffect(() => {
console.log(sort)
if (sort === "latest") {
setFilteredProducts(prev =>
[...prev].sort((a, b) => b.createdAt.localeCompare(a.createdAt))
)
} else if (sort === "asc") {
setFilteredProducts(prev =>
[...prev].sort((a, b) => a.price - b.price)
)
} else if (sort === "desc") {
setFilteredProducts(prev =>
[...prev].sort((a, b) => b.price - a.price)
)
} else {
setFilteredProducts(prev =>
[...prev].sort((a, b) => a.createdAt.localeCompare(b.createdAt))
)
}
}, [sort])
return (
<Container>
<Title>Popular In Store</Title>
<ProductsWrapper>
{filteredProducts.map(item => (
<Product key={item._id} item={item} />
))}
</ProductsWrapper>
</Container>
);
}
API Route - product.js
const router = require('express').Router()
const { verifyTokenAndAdmin } = require('./verifyToken')
const Product = require('../models/Product')
// .... (Other CRUD)
// GET ALL PRODUCTS
router.get("/", async(req, res) => {
const queryNew = req.query.new
const queryCategory = req.query.category
try {
let products = []
if(queryNew) {
products = await Product.find().sort({ createdAt: -1 }).limit(5)
} else if (queryCategory) {
products = await Product.find({
categories: {
$in: [queryCategory],
},
})
} else {
products = await Product.find()
}
res.status(200).json(products)
} catch(err) {
res.status(500).json(`Cannot fetch all products - ${err}`)
}
})
module.exports = router

According @idembele70's answer, I mistyped the initial state of filters to Array.
name="sort" on the Sort select.value="latest" with defaultValue="latest" for my Sort select bar. -> This makes the Latest option stop working so I don't think it can be used in this case?Code
const ProductList = () => {
const location = useLocation()
const category = location.pathname.split("/")[2]
const [filters, setFilters] = useState({});
const [sort, setSort] = useState("latest");
const handleFilters = (e) => {
const value = e.target.value
// When choosing default value, show all products:
if (value === "") {
setFilters({}) // Changed from array to object
} else {
setFilters({
...filters,
[e.target.name]: value,
})
}
}
...
<Filter>
<FilterText>Sort Products: </FilterText>
<Select name="sort" onChange={e => setSort(e.target.value)} >
<Option defaultValue="latest">Latest</Option>
<Option value="oldest">Oldest</Option>
<Option value="asc">Price ↑ (Low to High)</Option>
<Option value="desc">Price ↓ (High to Low)</Option>
</Select>
</Filter>
You shouldn't put the changed products to the state, as it makes it extra complex to keep it updated and you need to deal with various useEffect cases. Instead it's better to define sorting and filtering functions and apply them at the render time. This way you'll ensure the render result is always up-to-date with the data:
const filterProducts = (products) => {
if (!category) {
return products;
}
return products.filter(item =>
Object.entries(filters).every(([key, value]) =>
item[key].includes(value),
),
);
};
const sortProducts = (products) => {
switch (sort) {
case "asc":
return [...products].sort((a, b) => a.price - b.price);
case "desc":
return [...products].sort((a, b) => b.price - a.price);
case "latest":
default:
return [...products].sort((a, b) => a.createdAt.localeCompare(b.createdAt));
}
};
return (
<Container>
<Title>Popular In Store</Title>
<ProductsWrapper>
{sortProducts(filterProducts(products)).map(item => (
<Product key={item._id} item={item} />
))}
</ProductsWrapper>
</Container>
);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With