美好的一天,
我是 React 的新手,我正在制作我的第一个电子商务网站。我的问题是:如何按尺寸过滤我的产品?我实在想不出其中的逻辑。预先感谢您的回答。我也尝试过使用 Redux,但没有成功,它给了我以下错误:× 类型错误:无法读取未定义的属性“项目”
代码产品卡:
import React,{useState,useEffect} from 'react'
import {Link} from 'react-router-dom'
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import {Library} from '@fortawesome/fontawesome-svg-core'
import {faShoppingBasket} from '@fortawesome/free-solid-svg-icons'
import {useDispatch, useSelector} from 'react-redux'
import { listProduct, Filterproducts } from '../../actions/productActions'
function Product(props){
//default value is an array, because we've got data in an array
const [qty,setQty] = useState(1)
const productList = useSelector(state=>state.productList)
const{products,loading,error}=productList
const dispatch = useDispatch()
// handlefilter
useEffect(()=> {
dispatch(listProduct())
}, [])
// handle cart adding
const handleAddToCart = ()=> {
props.history.push('/cart/' + props.match.params.id + "?qty" +qty)
}
return(
// Check the loading before rendering products
loading? <div><h1 className="load">loading...</h1></div> :
error?<div>{error}</div>:
<ul className="products">
{products.map(product=> (
<li key={product.id} className="product">
<Link to={"/product/" + product.id}><div className="img" style={{background: `url(${product.img})`, backgroundSize: 'cover'}}></div></Link>
{/* LOOK OUT FOR TYPOS IN ROUTIING dont put':' after /, this only applies
when routing because the ": " implies for a parameter
In this case you can directly access product.id */}
<Link to={"/product/" + product.id}><h1>{product.name}</h1></Link>
<p> <small>€</small>{product.price}</p>
<div>size: {product.size}</div>
{product.qty > 0 ? <div><button onClick={handleAddToCart}>Add to cart</button> <div>{product.qty} left</div></div> : <div>out of stock</div> }
</li>
)
)}
</ul>
)
}
export default Product
产品操作代码:
import Axios from 'axios'
import {
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_DETAILS_FAIL,
FILTER_PRODUCTS_BY_PRICE,
FILTER_PRODUCTS_BY_SIZE} from '../constants/productConstants'
const listProduct = () => async(dispatch)=> {
try{
dispatch({type:PRODUCT_LIST_REQUEST})
fetch('http://localhost:5000/')
.then(res=> res.json())
.then(data=> dispatch({type: PRODUCT_LIST_SUCCESS, payload:data}) )
}
catch(error){
dispatch({type: PRODUCT_LIST_FAIL, payload:error.message})
}
}
// DETAILSPRODUCT
//we need to have a server in order to display the products
//it should get another link which contains the id of the product
const detailsProduct = (productId) => async(dispatch) => {
try{
dispatch({type: PRODUCT_DETAILS_REQUEST, payload: productId});
fetch('http://localhost:5000/' + productId)
.then(res=> res.json())
.then(data=> dispatch({type: PRODUCT_DETAILS_SUCCESS, payload:data}) )
}
catch(error){
dispatch({type: PRODUCT_DETAILS_FAIL, payload: error.message})
}
}
// Filter products
const Filterproducts = (products,size) => (dispatch) => {
return dispatch({
type:FILTER_PRODUCTS_BY_SIZE,
payload: {
size:size,
items:size === '' ? products : products.filter(a=> a.indexOf(size.toUpperCase()) >= 0)
}
})
}
export { listProduct, detailsProduct, Filterproducts }
代码产品减速器:
//two params are being accepted in the reducer func
//* state
import {
PRODUCT_DETAILS_FAIL,
PRODUCT_DETAILS_REQUEST,
PRODUCT_DETAILS_SUCCESS,
PRODUCT_LIST_FAIL,
PRODUCT_LIST_REQUEST,
PRODUCT_LIST_SUCCESS,
FILTER_PRODUCTS_BY_SIZE,
FILTER_PRODUCTS_BY_PRICE } from "../constants/productConstants";
//*action
function producListReducer(state = {products: [], filteredItems: [], size: ''}, action){
switch (action.type){
// case is like the if statement
//getting product
case PRODUCT_LIST_REQUEST:
return{loading: true};
// when products are loaded
case PRODUCT_LIST_SUCCESS:
return{loading:false, products: action.payload};
//when err occurs
case PRODUCT_LIST_FAIL:
return{loading: false, error: action.payload};
case FILTER_PRODUCTS_BY_SIZE:
return{...state, filteredItems: action.payload.items, size: action.payload.size}
default:
return state
}
}
function productDetailsReducer(state = {product: {}}, action){
switch (action.type){
// case is like the if statement
//getting product
case PRODUCT_DETAILS_REQUEST:
return{loading: true};
// when products are loaded
case PRODUCT_DETAILS_SUCCESS:
return{loading:false, product: action.payload};
//when err occurs
case PRODUCT_DETAILS_FAIL:
return{loading: false, error: action.payload};
default:
return state
}
}
export { producListReducer,productDetailsReducer }
错误消息意味着您正在尝试访问不存在的对象的关键“项目”。在发布的代码中唯一执行此操作的是在productReducers中,因此我建议您检查返回有效负载的函数“Filterproducts”。
过滤功能可以工作,但我不知道如何应用它。
搜索栏组件:
import React from 'react'
// Function Searchbar
//Looks for products
function Search(props){
return(
<input
type="search"
className="search"
placeholder={props.placeholder}
onChange={props.handleInput}/>
)
}
export default Search
产品成分:
返回( // 在渲染产品之前检查加载情况 加载中?加载中... : 错误?{错误}:
{products.map(product=> (
<li key={product.id} className="product">
<Link to={"/product/" + product.id}><div className="img" style={{background: `url(${product.img})`, backgroundSize: 'cover'}}></div></Link>
{/* LOOK OUT FOR TYPOS IN ROUTIING dont put':' after /, this only applies
when routing because the ": " implies for a parameter
In this case you can directly access product.id */}
<Link to={"/product/" + product.id}><h1>{product.name}</h1></Link>
<p> <small>€</small>{product.price}</p>
<div>size: {product.size}</div>
{product.qty > 0 ? <div><button onClick={handleAddToCart}>Add to cart</button> <div>{product.qty} left</div></div> : <div>out of stock</div> }
</li>
)
)}
</ul>
)
Store.js 中的搜索栏功能:
import React,{Component, useState} from 'react'
import Product from '../components/product'
import Nav from '../components/nav'
import Footer from '../components/footer'
import Searchbar from '../components/searchbar'
import Filter from '../components/filter'
class Store extends Component{
constructor(){
super()
this.state = {
products: [],
SearchProduct: ''
}
}
componentDidMount(){
fetch('http://localhost:5000/')
.then(res=> res.json())
.then(data=>this.setState({products:data}) )
}
render(){
let filteredProducts = this.state.products.filter((product)=> {
return product.name.toLowerCase().includes(this.state.SearchProduct.toLowerCase())
})
return (
<div>
<Nav/>
<div className="store">
<div className="title">
<h1>Store</h1>
</div>
<aside>
<Searchbar
placeholder="Search"
handleInput={(e)=> {
this.setState({SearchProduct: e.target.value})
}
}
/>
<ul>
<h2>Categories</h2>
<li>Women</li>
<li>Men</li>
<li>Clothing</li>
<li>Equipment</li>
</ul>
</aside>
<Filter/>
<Product filter={filteredProducts}/>
</div>
<Footer/>
</div>
)
}
}
export default Store
这里是应用价格、类别、排序和分页等过滤器的示例
const getPaginatedProducts = products
.slice(startIndex, endIndex)
.filter(item => {
if (categoryData !== "") {
if (item.category !== categoryData) return false;
}
if (price.min && price.max) {
if (item.sale_price < price.min || item.sale_price > price.max) return false;
} else {
if (price.min && item.sale_price < price.min) return false;
if (price.max && item.sale_price > price.max) return false;
}
return true;
})
.sort((a, b) => {
switch (sort) {
case "highToLow":
return comparePrices(b, a);
case "lowToHigh":
return comparePrices(a, b);
case "popular":
return b.top - a.top;
case "new":
return b.id - a.id;
default:
return 0;
}
});
//sort
const comparePrices = (a, b) => {
return a.sale_price - b.sale_price;
};
使用添加分页
showLimit = 10,
showPagination = 4;
let [pagination, setPagination] = useState([]);
let [limit, setLimit] = useState(showLimit);
let [pages, setPages] = useState(Math.ceil(products.length / limit));
let [currentPage, setCurrentPage] = useState(1);
const cratePagination = () => {
// set pagination
let arr = new Array(Math.ceil(products.length / limit))
.fill()
.map((_, idx) => idx + 1);
setPagination(arr);
setPages(Math.ceil(products.length / limit));
};
const startIndex = currentPage * limit - limit;
const endIndex = startIndex + limit;
<Pagination
getPaginationGroup={
getPaginationGroup
}
currentPage={currentPage}
pages={pages}
next={next}
prev={prev}
handleActive={handleActive}
/>
function Pagination({
prev,
currentPage,
getPaginationGroup,
next,
pages,
handleActive,
}) {
return (
<>
<ul className="pagination justify-content-start">
{getPaginationGroup.length <= 0 ? null : (
<li onClick={prev} className="page-item">
{currentPage === 1 ? null : (
<a className="page-link">
<i className="fi-rs-angle-double-small-left"></i>
</a>
)}
</li>
)}
{getPaginationGroup.map((item, index) => {
return (
<li
onClick={() => handleActive(item)}
key={index}
className={
currentPage === item
? "page-item active"
: "page-item"
}
>
<a className="page-link">{item}</a>
</li>
);
})}
{getPaginationGroup.length <= 0 ? null : (
<li onClick={next} className="page-item">
{currentPage >= pages ? null : (
<a className="page-link">
<i className="fi-rs-angle-double-small-right"></i>
</a>
)}
</li>
)}
</ul>
{getPaginationGroup.length <= 0 ? null : (
<p>
show {currentPage} of {pages}
</p>
)}
</>
);
}