I’m building an E-commerce app with React and I stumbled on a problem that React doesn’t the UI render based the initial state when first start the page.
Problem description:
- I have a
sort
state which has the initial state of"latest"
, based on this Sorting functionality – ifsort
has a value of"latest"
– it will sort and return the newest items first. - But on start or when I refresh the page, the default value and state of sort will still be “latest” but the UI just display the oldest items first.
- I have to click on other option and then choose the Latest option again for the sort logic to go through. When I refresh the page, the problem is back.
- The logic for sorting other values works fine. In the demo below, you can see I log out the
sort
current state. On start, thesort
value is already"latest"
.
-> What would be the case here that makes React not render the items based on initial state and how can we fix it?
Below is my code:
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>
);
}
Demo:
- Explain demo: On start, it renders oldest items first. Have to choose another option and then return to the latest option for it to render. But in the console, the initial state of sort is already “latest” but it doesn’t match with the useEffect sorting logic.