在 React Redux 中打开每个产品时增加查看次数
Increase view count when each product is opened in React Redux
我想要的是增加每个产品的数量,当它被打开(查看)时,使用 react redux。
AllProductsPage.js(页面从这里开始)
import React, { useState } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Link } from "react-router-dom";
import ProductList from "./ProductList";
import Pagination from './Pagination'
import * as productActions from "../redux/actions/productActions";
import * as userActions from '../redux/actions/userActions'
import { Button } from "react-bootstrap";
import {FiSearch} from 'react-icons/fi'
import { Container, Row, Col} from "react-bootstrap";
const AllProductsPage =(props)=> {
const [quantity, showQuantity] = useState(true);
const [price, showPrice] = useState(true);
const [manufacturer,showManufacturer] = useState(true);
const data = {quantity,price,manufacturer};
const [search,setSearch]=useState("");
const loggedIn = props.loggedIn;
//Pagination Logic
const [currentPage,setCurrentPage] = useState(1)
const postsPerPage = 9
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = props.products.slice(indexOfFirstPost,indexOfLastPost)
//Change the page
const paginate =(pageNumber)=>{
setCurrentPage(pageNumber)
}
//const filteredSearch = props.products && props.products.filter(product=>product.name.toLowerCase().indexOf(search.toLowerCase())!==-1).sort( (a,b)=>(a.id>b.id)?1:-1 );
const filteredSearch = currentPosts && currentPosts.filter(product=>product.name.toLowerCase().indexOf(search.toLowerCase())!==-1).sort( (a,b)=>(a.id>b.id)?1:-1 );
return (
<div>
<div style={{"display":"flex","paddingTop":"30px"}} className="container">
{ loggedIn && <Link to="/addProduct"><Button variant="primary">Add Product</Button>{" "}</Link> }
<span style={{"marginLeft":"auto"}}><input type="text" onChange={event=>setSearch(event.target.value)}/> {" "} <FiSearch size="20px"/> </span>
</div>
<div style={{"display":"flex","justifyContent":"flex-end","alignItems":"space-between","paddingTop":"6px"}} className="container" >
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={quantity} onClick={()=>showQuantity(!quantity)}/>{" "}Quantity</label>
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={price} onClick={()=>showPrice(!price)}/>{" "}Price </label>
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={manufacturer} onClick={()=>showManufacturer(!manufacturer)}/>{" "}Manufacturer </label>
</div>
<hr></hr>
<div style={{minHeight:"100vh"}}>
<ProductList
products={filteredSearch}
data={data}
togglePrice={showPrice}
toggleQuantity={showQuantity}
toggleManufacturer={showManufacturer}
loggedIn={props.loggedIn}
/>
<br />
<Container>
<Row>
<Col></Col>
<Col xs="auto" sm="auto" md="auto" lg="auto">
<Pagination postsPerPage={postsPerPage} totalPosts={props.products.length} paginate={paginate} />
</Col>
<Col></Col>
</Row>
</Container>
</div>
<footer>
<p style={{"textAlign":"center","backgroundColor":"#333","color":"white","padding":"20px"}}>Copyright @2020, Rohit K F</p>
</footer>
</div>
);
}
function mapStateToProps(state, ownProps) {
return {
products: state.products,
users : state.users
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
userAction : bindActionCreators(userActions,dispatch)
};
}
export default (connect(mapStateToProps, mapDispatchToProps))(AllProductsPage);
ProductList.js(然后它获取每个产品并将其传递给Product.js)
import React from "react";
import Product from "./Product";
import { Container, Row, Col} from "react-bootstrap";
const chunk = (arr, chunkSize = 1, cache = []) => {
const tmp = [...arr]
if (chunkSize <= 0) return cache
while (tmp.length) cache.push(tmp.splice(0, chunkSize))
return cache
}
const ProductList = (props) => {
const productsChunks = chunk(props.products, 3)
const rows = productsChunks.map((productChunk, index) => {
const productsCols = productChunk.map((product, index) => {
return (
<Col xs="auto" sm="auto" md="auto" lg="auto" key={product.id} style={{"paddingBottom":"20px"}}>
<Product
key={product.id}
id={product.id}
quantity={product.quantity}
price={product.price}
name={product.name}
description={product.description}
manufacturer={product.manufacturer}
{...props}
/>
</Col>
);
});
return (
<Row key={index} style={{"paddingBottom":"20px"}}>
{productsCols}
</Row>
)});
return (
<Container>
{rows}
</Container>
)
}
export default ProductList;
Product.js(这里我们展示每个产品)
import React,{useState} from "react";
import { Link } from "react-router-dom";
import { Prompt, withRouter } from "react-router";
import { connect } from "react-redux";
import * as productActions from "../redux/actions/productActions";
import { bindActionCreators } from "redux";
import { Card, Button } from "react-bootstrap";
import toastr from "toastr";
import EditProduct from './EditProduct'
import {MdDelete,MdVisibility,MdCreate} from 'react-icons/md'
const Product = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const isLoggedIn = props.loggedIn
const checkUser = (e) => {
if (!isLoggedIn) {
e.preventDefault();
toastr.options = { positionClass: "toast-top-full-width",hideDuration: 300,timeOut: 2000,};
toastr.clear();
setTimeout(() => toastr.warning("Login to view details"), 0);
}
};
const deleteProduct = () => {
props.actions.deleteProduct(props.id)
};
//<Link to={'/ProductDetail/'+props.id} >
const product = {
id :props.id,name:props.name,quantity:props.quantity,description:props.description,manufacturer:props.manufacturer,price:props.price
}
return (
<>
<Card style={{ width: "18rem", "borderRadius":"30px","border":"3px solid" }}>
{isLoggedIn && (
<Prompt when={isLoggedIn}
message={(location) => location.pathname.includes("/ProductDetail/") ? `Are you sure you want to view the details ?` : true }
/>
)}
<Card.Body>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.name} </Card.Title>
{props.data.quantity && ( <Card.Text> Quantity : {props.quantity} </Card.Text> )}
{props.data.manufacturer && <Card.Text> Manufacturer : {props.manufacturer}</Card.Text>}
{props.data.price && <Card.Text>$ {props.price}</Card.Text>}
<div style={{ display: "flex", justifyContent: "space-around" }}>
<Link
to={{
pathname: `/ProductDetail/${props.id}`,
productName: {
id: props.id,
name: props.name,
price: props.price,
quantity: props.quantity,
description: props.description,
manufacturer: props.manufacturer,
},
}}
>
<Button variant="primary" onClick={(event) => checkUser(event)} style={{ "fontWeight":"bold" }} >
{!isLoggedIn && <span style={{"paddingRight":"5px"}}>View</span> }
{!isLoggedIn && <MdVisibility color="black"/> }
{isLoggedIn && <MdVisibility/>}
</Button>
</Link>
{isLoggedIn && <Button variant="success" style={{"fontWeight":"bold" }} onClick={() => handleShow()} ><MdCreate/></Button> }
{isLoggedIn && <Button variant="danger" style={{"fontWeight":"bold" }} onClick={() => deleteProduct()} ><MdDelete/> </Button>}
</div>
</Card.Body>
</Card>
<EditProduct show={show} handleClose={handleClose} actions={props.actions} product={product}/>
</>
);
};
function mapStateToProps(state, ownProps) {
return {
products: state.products,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(Product));
ProductDetail.js(点击查看会进入该页面查看商品详情)
import React from 'react';
import { Link} from 'react-router-dom';
import {withRouter} from 'react-router'
import {Button, Card} from 'react-bootstrap'
const ProductDetail=(props)=>{
console.log(props)
const style={"display":"flex", "justifyContent":"center","alignItems":"center"}
return(
<div style={style}>
<Card style={{ width: "18rem","borderRadius":"30px" }}>
<Card.Body style={{style}}>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.location.productName.name} </Card.Title>
<Card.Text><strong>Quantity :</strong>{props.location.productName.quantity}</Card.Text>
<Card.Text><strong>Price :</strong>{props.location.productName.price}</Card.Text>
<Card.Text><strong>Manufacturer:</strong>{props.location.productName.manufacturer}</Card.Text>
<Card.Text><strong>Description :</strong>{props.location.productName.description}</Card.Text>
<div>
<Link to="/"><Button variant="primary" style={{ height: "6vh","fontWeight":"bold" }}>Back</Button></Link>
</div>
</Card.Body>
</Card>
</div>
);
}
export default withRouter(ProductDetail);
ProductReducer.js
import initialState from "./initialState";
import * as actionTypes from "../actions/actionTypes";
export default function productReducer(state = initialState.products, action) {
switch (action.type) {
case actionTypes.INIT:
return action.products;
case actionTypes.ADD:
return [...state, Object.assign({}, action.product)];
case actionTypes.DELETE:
return [...state.filter((product) => product.id !== action.id)];
case actionTypes.UPDATE:
return [
...state.filter((product) => product.id !== action.product.id),
Object.assign({}, action.product),
];
case actionTypes.VIEW:
return [
...state[action.product.id],
Object.assign({},action.product.view)
]
default:
return state;
}
}
ProductActions.js
import dataApi from "../../server/dataAPI";
import * as actionTypes from "../actions/actionTypes";
//======================LOADING A PRODUCT
export function loadProduct() {
return function (dispatch) {
return dataApi
.getAllProducts()
.then((products) => {
dispatch({ type: actionTypes.INIT, products });
})
.catch((error) => {
throw error;
});
};
}
//==========================ADDING A PRODUCT
export function addProduct(product) {
return function (dispatch) {
return dataApi
.addProduct(product)
.then((product) => {
dispatch({ type: actionTypes.ADD, product });
})
.catch((error) => {
throw error;
});
};
}
//==========================DELETE A PRODUCT
export function deleteProduct(id) {
return function (dispatch) {
return dataApi
.deleteProduct(id)
.then((product) => {
dispatch({ type: actionTypes.DELETE, id});
})
.catch((error) => {
throw error;
});
};
}
//==========================UPDATE A PRODUCT
export function updateProduct(product) {
return function (dispatch) {
return dataApi
.updateProduct(product)
.then((product) => {
dispatch({ type: actionTypes.UPDATE, product });
})
.catch((error) => {
throw error;
});
};
}
//Increase View Count of product
export function addView(product){
return function (dispatch){
return dataApi.addView(product)
.then(product=>{
dispatch({type:actionTypes.VIEW, product})
})
}
}
dataAPI.js(使用 axios 添加、删除、更新到 json 服务器)
import axios from 'axios'
class dataAPI {
static getAllProducts() {
return axios.get('http://localhost:4000/products?_sort=id&_order=asc').then(response=>response.data);
}
static addProduct(product) {
return axios.post('http://localhost:4000/products',product).then(response=>response.data);
}
static updateProduct(product){
return axios.patch('http://localhost:4000/products/'+product.id,product)
.then(response=>response.data);
}
static deleteProduct(id){
return axios.delete(`http://localhost:4000/products/${id}`).then(response=>response.data);
}
static getAllUsers(){
return axios.get('http://localhost:4000/users').then(response=>response.data);
}
static addUser(user) {
return axios.post('http://localhost:4000/users',user).then(response=>response.data);
}
}
export default dataAPI;
db.json(包含所有数据的文件)
{
"products": [
{
"id": 1,
"name": "Moto G5 Ultra",
"quantity": 3,
"price": 10000,
"description": "Moto G5",
"manufacturer": "Motorola",
"views" : 0
},
{
"id": 2,
"name": "Racold Geyser",
"quantity": 2,
"price": 60000,
"description": "Moto G5",
"manufacturer": "Motorola",
"views" : 0
},
{
"name": "Lenovo G5",
"quantity": 3,
"price": 55000,
"manufacturer": "Lenovo",
"description": "A gaming laptop",
"id": 3,
"views" : 0
},
{
"name": "Acer Swift ",
"quantity": 5,
"price": 35000,
"manufacturer": "Acer",
"description": "Business Laptop",
"id": 4,
"views" : 0
},
{
"name": "Acer Nitro 7",
"quantity": 4,
"price": 75000,
"manufacturer": "Acer",
"description": "A gaming laptop",
"id": 5,
"views" : 0
},
"users": [
{
"id": 1,
"email": "vi@gmail.com",
"password": "truth",
"name": {
"firstName": "Rick",
"lastName": "Garner"
},
"location": "Canada",
"mobile": "55643980"
},
{
"id": 2,
"email": "t@t.com",
"password": "123",
"name": {
"firstName": "Ram",
"lastName": "Shankar"
},
"location": "Delhi",
"mobile": "9895454860"
},
{
"email": "e@e.com",
"password": "123456789",
"name": {
"firstName": "RAGAV",
"lastName": "Shant"
},
"location": "Karnataka",
"mobile": "1234567891",
"id": 3
},
{
"email": "k@k.com",
"password": "123456789",
"name": {
"firstName": "sd",
"lastName": "dv"
},
"location": "dfv",
"mobile": "12345678231",
"id": 4
}
]
}
您可能希望在 ProductDetail.jsx
页面内的 useEffect 中发送更新产品操作。
useEffect(() => {
updateProduct({
...props.location.productName,
views: props.location.productName + 1,
});
}, []);
当然你还需要从Product.jsx
传递views
。
这将增加用户每次 opens/refreshes 页面的浏览量。
编辑:
如果您想要单独的 API 端点来增加观看次数,您可以在服务器端实现其增加逻辑。在这种情况下,它不会更改当前减速器文件 ProductReducer.js
中的任何内容。
但我认为没有必要。出于这个原因,您可以使用 updateProduct API 。在这种情况下也不需要更换减速器。
编辑 2:
如果 addView
API 返回产品 ID 和增量视图,那么你可以将 reducer 写成 -
case actionTypes.VIEW:
return [
...state.map((product) => {
if (product.id === action.product.id) {
product.views = action.product.views;
}
return product;
})
]
所以我所做的是在我的 ProductDetail.js 文件中添加了一个 useEffect() 并从那里启动了 Action。
ProductDetail.js
import React,{useEffect} from 'react';
import { Link} from 'react-router-dom';
import {withRouter} from 'react-router'
import {Button, Card} from 'react-bootstrap'
import { connect } from "react-redux";
import * as productActions from "../redux/actions/productActions";
import { bindActionCreators } from "redux";
const ProductDetail=(props)=>{
useEffect(() => {
console.log("PROPIES ",props.location.productName.id+" "+props.location.productName.views)
props.actions.addView(props.location.productName.id,props.location.productName.views)
},[props.actions,props.location.productName.id,props.location.productName.views])
const style={"display":"flex", "justifyContent":"center","alignItems":"center","minHeight":"100vh"}
return(
<div style={style}>
<Card style={{ width: "18rem","borderRadius":"30px" }} >
<Card.Body style={{style}}>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.location.productName.name} </Card.Title>
<Card.Text><strong>Quantity :</strong>{props.location.productName.quantity}</Card.Text>
<Card.Text><strong>Price :</strong>{props.location.productName.price}</Card.Text>
<Card.Text><strong>Manufacturer:</strong>{props.location.productName.manufacturer}</Card.Text>
<Card.Text><strong>Description :</strong>{props.location.productName.description}</Card.Text>
<div>
<Link to="/"><Button variant="primary" style={{ height: "6vh","fontWeight":"bold" }}>Back</Button></Link>
</div>
</Card.Body>
</Card>
</div>
);
}
function mapStateToProps(state, ownProps) {
return {
products: state.products,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(ProductDetail));
然后触发这个动作
//Increase View Count of product
export function addView(id,count){
console.log("func called")
return function (dispatch){
console.log("api to be called")
return dataApi.addView(id,count)
.then((product)=>{
console.log("dispatched")
dispatch({type:actionTypes.VIEW, id: product.id})
})
}
}
所以它先更新服务器上的视图,然后在reducer状态下更新
dataAPI.js
static addView(id,count){
return axios.patch('http://localhost:4000/products/'+id,{views:count+1})
.then(response=>response.data);
}
productReducer.js
import initialState from "./initialState";
import * as actionTypes from "../actions/actionTypes";
export default function productReducer(state = initialState.products, action) {
switch (action.type) {
case actionTypes.INIT:
return action.products;
case actionTypes.ADD:
return [...state, Object.assign({}, action.product)];
case actionTypes.DELETE:
return [...state.filter((product) => product.id !== action.id)];
case actionTypes.UPDATE:
return [
...state.filter((product) => product.id !== action.product.id),
Object.assign({}, action.product),
].sort( (a,b)=>(a.id>b.id)?1:-1 );
case actionTypes.VIEW:
let prod = [...state][action.id-1];
prod.views++;
//eslint-disable-next-line
let addView =()=>( [
...state.filter(product => product.id !== action.id),
Object.assign({}, prod)
])
return state;
default:
return state;
}
}
我不得不像这样在 switch 中编写 ActionType.VIEW 案例
case actionTypes.VIEW:
let prod = [...state][action.id-1];
prod.views++;
//eslint-disable-next-line
let addView =()=>( [
...state.filter(product => product.id !== action.id),
Object.assign({}, prod)
])
return state;
我不得不将状态修改部分放在一个名为 addView() 的函数中,否则我会看到该函数被无限地重复调用。如果有人可以帮我解决这个问题,我将不胜感激
我想要的是增加每个产品的数量,当它被打开(查看)时,使用 react redux。
AllProductsPage.js(页面从这里开始)
import React, { useState } from "react";
import { connect } from "react-redux";
import { bindActionCreators } from "redux";
import { Link } from "react-router-dom";
import ProductList from "./ProductList";
import Pagination from './Pagination'
import * as productActions from "../redux/actions/productActions";
import * as userActions from '../redux/actions/userActions'
import { Button } from "react-bootstrap";
import {FiSearch} from 'react-icons/fi'
import { Container, Row, Col} from "react-bootstrap";
const AllProductsPage =(props)=> {
const [quantity, showQuantity] = useState(true);
const [price, showPrice] = useState(true);
const [manufacturer,showManufacturer] = useState(true);
const data = {quantity,price,manufacturer};
const [search,setSearch]=useState("");
const loggedIn = props.loggedIn;
//Pagination Logic
const [currentPage,setCurrentPage] = useState(1)
const postsPerPage = 9
const indexOfLastPost = currentPage * postsPerPage;
const indexOfFirstPost = indexOfLastPost - postsPerPage;
const currentPosts = props.products.slice(indexOfFirstPost,indexOfLastPost)
//Change the page
const paginate =(pageNumber)=>{
setCurrentPage(pageNumber)
}
//const filteredSearch = props.products && props.products.filter(product=>product.name.toLowerCase().indexOf(search.toLowerCase())!==-1).sort( (a,b)=>(a.id>b.id)?1:-1 );
const filteredSearch = currentPosts && currentPosts.filter(product=>product.name.toLowerCase().indexOf(search.toLowerCase())!==-1).sort( (a,b)=>(a.id>b.id)?1:-1 );
return (
<div>
<div style={{"display":"flex","paddingTop":"30px"}} className="container">
{ loggedIn && <Link to="/addProduct"><Button variant="primary">Add Product</Button>{" "}</Link> }
<span style={{"marginLeft":"auto"}}><input type="text" onChange={event=>setSearch(event.target.value)}/> {" "} <FiSearch size="20px"/> </span>
</div>
<div style={{"display":"flex","justifyContent":"flex-end","alignItems":"space-between","paddingTop":"6px"}} className="container" >
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={quantity} onClick={()=>showQuantity(!quantity)}/>{" "}Quantity</label>
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={price} onClick={()=>showPrice(!price)}/>{" "}Price </label>
<label style={{"padding":"0px 5px 0px 2px","color":"white"}}><input type="checkbox" defaultChecked={manufacturer} onClick={()=>showManufacturer(!manufacturer)}/>{" "}Manufacturer </label>
</div>
<hr></hr>
<div style={{minHeight:"100vh"}}>
<ProductList
products={filteredSearch}
data={data}
togglePrice={showPrice}
toggleQuantity={showQuantity}
toggleManufacturer={showManufacturer}
loggedIn={props.loggedIn}
/>
<br />
<Container>
<Row>
<Col></Col>
<Col xs="auto" sm="auto" md="auto" lg="auto">
<Pagination postsPerPage={postsPerPage} totalPosts={props.products.length} paginate={paginate} />
</Col>
<Col></Col>
</Row>
</Container>
</div>
<footer>
<p style={{"textAlign":"center","backgroundColor":"#333","color":"white","padding":"20px"}}>Copyright @2020, Rohit K F</p>
</footer>
</div>
);
}
function mapStateToProps(state, ownProps) {
return {
products: state.products,
users : state.users
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
userAction : bindActionCreators(userActions,dispatch)
};
}
export default (connect(mapStateToProps, mapDispatchToProps))(AllProductsPage);
ProductList.js(然后它获取每个产品并将其传递给Product.js)
import React from "react";
import Product from "./Product";
import { Container, Row, Col} from "react-bootstrap";
const chunk = (arr, chunkSize = 1, cache = []) => {
const tmp = [...arr]
if (chunkSize <= 0) return cache
while (tmp.length) cache.push(tmp.splice(0, chunkSize))
return cache
}
const ProductList = (props) => {
const productsChunks = chunk(props.products, 3)
const rows = productsChunks.map((productChunk, index) => {
const productsCols = productChunk.map((product, index) => {
return (
<Col xs="auto" sm="auto" md="auto" lg="auto" key={product.id} style={{"paddingBottom":"20px"}}>
<Product
key={product.id}
id={product.id}
quantity={product.quantity}
price={product.price}
name={product.name}
description={product.description}
manufacturer={product.manufacturer}
{...props}
/>
</Col>
);
});
return (
<Row key={index} style={{"paddingBottom":"20px"}}>
{productsCols}
</Row>
)});
return (
<Container>
{rows}
</Container>
)
}
export default ProductList;
Product.js(这里我们展示每个产品)
import React,{useState} from "react";
import { Link } from "react-router-dom";
import { Prompt, withRouter } from "react-router";
import { connect } from "react-redux";
import * as productActions from "../redux/actions/productActions";
import { bindActionCreators } from "redux";
import { Card, Button } from "react-bootstrap";
import toastr from "toastr";
import EditProduct from './EditProduct'
import {MdDelete,MdVisibility,MdCreate} from 'react-icons/md'
const Product = (props) => {
const [show, setShow] = useState(false);
const handleClose = () => setShow(false);
const handleShow = () => setShow(true);
const isLoggedIn = props.loggedIn
const checkUser = (e) => {
if (!isLoggedIn) {
e.preventDefault();
toastr.options = { positionClass: "toast-top-full-width",hideDuration: 300,timeOut: 2000,};
toastr.clear();
setTimeout(() => toastr.warning("Login to view details"), 0);
}
};
const deleteProduct = () => {
props.actions.deleteProduct(props.id)
};
//<Link to={'/ProductDetail/'+props.id} >
const product = {
id :props.id,name:props.name,quantity:props.quantity,description:props.description,manufacturer:props.manufacturer,price:props.price
}
return (
<>
<Card style={{ width: "18rem", "borderRadius":"30px","border":"3px solid" }}>
{isLoggedIn && (
<Prompt when={isLoggedIn}
message={(location) => location.pathname.includes("/ProductDetail/") ? `Are you sure you want to view the details ?` : true }
/>
)}
<Card.Body>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.name} </Card.Title>
{props.data.quantity && ( <Card.Text> Quantity : {props.quantity} </Card.Text> )}
{props.data.manufacturer && <Card.Text> Manufacturer : {props.manufacturer}</Card.Text>}
{props.data.price && <Card.Text>$ {props.price}</Card.Text>}
<div style={{ display: "flex", justifyContent: "space-around" }}>
<Link
to={{
pathname: `/ProductDetail/${props.id}`,
productName: {
id: props.id,
name: props.name,
price: props.price,
quantity: props.quantity,
description: props.description,
manufacturer: props.manufacturer,
},
}}
>
<Button variant="primary" onClick={(event) => checkUser(event)} style={{ "fontWeight":"bold" }} >
{!isLoggedIn && <span style={{"paddingRight":"5px"}}>View</span> }
{!isLoggedIn && <MdVisibility color="black"/> }
{isLoggedIn && <MdVisibility/>}
</Button>
</Link>
{isLoggedIn && <Button variant="success" style={{"fontWeight":"bold" }} onClick={() => handleShow()} ><MdCreate/></Button> }
{isLoggedIn && <Button variant="danger" style={{"fontWeight":"bold" }} onClick={() => deleteProduct()} ><MdDelete/> </Button>}
</div>
</Card.Body>
</Card>
<EditProduct show={show} handleClose={handleClose} actions={props.actions} product={product}/>
</>
);
};
function mapStateToProps(state, ownProps) {
return {
products: state.products,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(Product));
ProductDetail.js(点击查看会进入该页面查看商品详情)
import React from 'react';
import { Link} from 'react-router-dom';
import {withRouter} from 'react-router'
import {Button, Card} from 'react-bootstrap'
const ProductDetail=(props)=>{
console.log(props)
const style={"display":"flex", "justifyContent":"center","alignItems":"center"}
return(
<div style={style}>
<Card style={{ width: "18rem","borderRadius":"30px" }}>
<Card.Body style={{style}}>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.location.productName.name} </Card.Title>
<Card.Text><strong>Quantity :</strong>{props.location.productName.quantity}</Card.Text>
<Card.Text><strong>Price :</strong>{props.location.productName.price}</Card.Text>
<Card.Text><strong>Manufacturer:</strong>{props.location.productName.manufacturer}</Card.Text>
<Card.Text><strong>Description :</strong>{props.location.productName.description}</Card.Text>
<div>
<Link to="/"><Button variant="primary" style={{ height: "6vh","fontWeight":"bold" }}>Back</Button></Link>
</div>
</Card.Body>
</Card>
</div>
);
}
export default withRouter(ProductDetail);
ProductReducer.js
import initialState from "./initialState";
import * as actionTypes from "../actions/actionTypes";
export default function productReducer(state = initialState.products, action) {
switch (action.type) {
case actionTypes.INIT:
return action.products;
case actionTypes.ADD:
return [...state, Object.assign({}, action.product)];
case actionTypes.DELETE:
return [...state.filter((product) => product.id !== action.id)];
case actionTypes.UPDATE:
return [
...state.filter((product) => product.id !== action.product.id),
Object.assign({}, action.product),
];
case actionTypes.VIEW:
return [
...state[action.product.id],
Object.assign({},action.product.view)
]
default:
return state;
}
}
ProductActions.js
import dataApi from "../../server/dataAPI";
import * as actionTypes from "../actions/actionTypes";
//======================LOADING A PRODUCT
export function loadProduct() {
return function (dispatch) {
return dataApi
.getAllProducts()
.then((products) => {
dispatch({ type: actionTypes.INIT, products });
})
.catch((error) => {
throw error;
});
};
}
//==========================ADDING A PRODUCT
export function addProduct(product) {
return function (dispatch) {
return dataApi
.addProduct(product)
.then((product) => {
dispatch({ type: actionTypes.ADD, product });
})
.catch((error) => {
throw error;
});
};
}
//==========================DELETE A PRODUCT
export function deleteProduct(id) {
return function (dispatch) {
return dataApi
.deleteProduct(id)
.then((product) => {
dispatch({ type: actionTypes.DELETE, id});
})
.catch((error) => {
throw error;
});
};
}
//==========================UPDATE A PRODUCT
export function updateProduct(product) {
return function (dispatch) {
return dataApi
.updateProduct(product)
.then((product) => {
dispatch({ type: actionTypes.UPDATE, product });
})
.catch((error) => {
throw error;
});
};
}
//Increase View Count of product
export function addView(product){
return function (dispatch){
return dataApi.addView(product)
.then(product=>{
dispatch({type:actionTypes.VIEW, product})
})
}
}
dataAPI.js(使用 axios 添加、删除、更新到 json 服务器)
import axios from 'axios'
class dataAPI {
static getAllProducts() {
return axios.get('http://localhost:4000/products?_sort=id&_order=asc').then(response=>response.data);
}
static addProduct(product) {
return axios.post('http://localhost:4000/products',product).then(response=>response.data);
}
static updateProduct(product){
return axios.patch('http://localhost:4000/products/'+product.id,product)
.then(response=>response.data);
}
static deleteProduct(id){
return axios.delete(`http://localhost:4000/products/${id}`).then(response=>response.data);
}
static getAllUsers(){
return axios.get('http://localhost:4000/users').then(response=>response.data);
}
static addUser(user) {
return axios.post('http://localhost:4000/users',user).then(response=>response.data);
}
}
export default dataAPI;
db.json(包含所有数据的文件)
{
"products": [
{
"id": 1,
"name": "Moto G5 Ultra",
"quantity": 3,
"price": 10000,
"description": "Moto G5",
"manufacturer": "Motorola",
"views" : 0
},
{
"id": 2,
"name": "Racold Geyser",
"quantity": 2,
"price": 60000,
"description": "Moto G5",
"manufacturer": "Motorola",
"views" : 0
},
{
"name": "Lenovo G5",
"quantity": 3,
"price": 55000,
"manufacturer": "Lenovo",
"description": "A gaming laptop",
"id": 3,
"views" : 0
},
{
"name": "Acer Swift ",
"quantity": 5,
"price": 35000,
"manufacturer": "Acer",
"description": "Business Laptop",
"id": 4,
"views" : 0
},
{
"name": "Acer Nitro 7",
"quantity": 4,
"price": 75000,
"manufacturer": "Acer",
"description": "A gaming laptop",
"id": 5,
"views" : 0
},
"users": [
{
"id": 1,
"email": "vi@gmail.com",
"password": "truth",
"name": {
"firstName": "Rick",
"lastName": "Garner"
},
"location": "Canada",
"mobile": "55643980"
},
{
"id": 2,
"email": "t@t.com",
"password": "123",
"name": {
"firstName": "Ram",
"lastName": "Shankar"
},
"location": "Delhi",
"mobile": "9895454860"
},
{
"email": "e@e.com",
"password": "123456789",
"name": {
"firstName": "RAGAV",
"lastName": "Shant"
},
"location": "Karnataka",
"mobile": "1234567891",
"id": 3
},
{
"email": "k@k.com",
"password": "123456789",
"name": {
"firstName": "sd",
"lastName": "dv"
},
"location": "dfv",
"mobile": "12345678231",
"id": 4
}
]
}
您可能希望在 ProductDetail.jsx
页面内的 useEffect 中发送更新产品操作。
useEffect(() => {
updateProduct({
...props.location.productName,
views: props.location.productName + 1,
});
}, []);
当然你还需要从Product.jsx
传递views
。
这将增加用户每次 opens/refreshes 页面的浏览量。
编辑:
如果您想要单独的 API 端点来增加观看次数,您可以在服务器端实现其增加逻辑。在这种情况下,它不会更改当前减速器文件 ProductReducer.js
中的任何内容。
但我认为没有必要。出于这个原因,您可以使用 updateProduct API 。在这种情况下也不需要更换减速器。
编辑 2:
如果 addView
API 返回产品 ID 和增量视图,那么你可以将 reducer 写成 -
case actionTypes.VIEW:
return [
...state.map((product) => {
if (product.id === action.product.id) {
product.views = action.product.views;
}
return product;
})
]
所以我所做的是在我的 ProductDetail.js 文件中添加了一个 useEffect() 并从那里启动了 Action。
ProductDetail.js
import React,{useEffect} from 'react';
import { Link} from 'react-router-dom';
import {withRouter} from 'react-router'
import {Button, Card} from 'react-bootstrap'
import { connect } from "react-redux";
import * as productActions from "../redux/actions/productActions";
import { bindActionCreators } from "redux";
const ProductDetail=(props)=>{
useEffect(() => {
console.log("PROPIES ",props.location.productName.id+" "+props.location.productName.views)
props.actions.addView(props.location.productName.id,props.location.productName.views)
},[props.actions,props.location.productName.id,props.location.productName.views])
const style={"display":"flex", "justifyContent":"center","alignItems":"center","minHeight":"100vh"}
return(
<div style={style}>
<Card style={{ width: "18rem","borderRadius":"30px" }} >
<Card.Body style={{style}}>
<Card.Title style={{"fontSize":"30px","fontWeight":"bold","display":"flex", "justifyContent":"center"}}> {props.location.productName.name} </Card.Title>
<Card.Text><strong>Quantity :</strong>{props.location.productName.quantity}</Card.Text>
<Card.Text><strong>Price :</strong>{props.location.productName.price}</Card.Text>
<Card.Text><strong>Manufacturer:</strong>{props.location.productName.manufacturer}</Card.Text>
<Card.Text><strong>Description :</strong>{props.location.productName.description}</Card.Text>
<div>
<Link to="/"><Button variant="primary" style={{ height: "6vh","fontWeight":"bold" }}>Back</Button></Link>
</div>
</Card.Body>
</Card>
</div>
);
}
function mapStateToProps(state, ownProps) {
return {
products: state.products,
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(productActions, dispatch),
};
}
export default connect(mapStateToProps,mapDispatchToProps)(withRouter(ProductDetail));
然后触发这个动作
//Increase View Count of product
export function addView(id,count){
console.log("func called")
return function (dispatch){
console.log("api to be called")
return dataApi.addView(id,count)
.then((product)=>{
console.log("dispatched")
dispatch({type:actionTypes.VIEW, id: product.id})
})
}
}
所以它先更新服务器上的视图,然后在reducer状态下更新
dataAPI.js
static addView(id,count){
return axios.patch('http://localhost:4000/products/'+id,{views:count+1})
.then(response=>response.data);
}
productReducer.js
import initialState from "./initialState";
import * as actionTypes from "../actions/actionTypes";
export default function productReducer(state = initialState.products, action) {
switch (action.type) {
case actionTypes.INIT:
return action.products;
case actionTypes.ADD:
return [...state, Object.assign({}, action.product)];
case actionTypes.DELETE:
return [...state.filter((product) => product.id !== action.id)];
case actionTypes.UPDATE:
return [
...state.filter((product) => product.id !== action.product.id),
Object.assign({}, action.product),
].sort( (a,b)=>(a.id>b.id)?1:-1 );
case actionTypes.VIEW:
let prod = [...state][action.id-1];
prod.views++;
//eslint-disable-next-line
let addView =()=>( [
...state.filter(product => product.id !== action.id),
Object.assign({}, prod)
])
return state;
default:
return state;
}
}
我不得不像这样在 switch 中编写 ActionType.VIEW 案例
case actionTypes.VIEW:
let prod = [...state][action.id-1];
prod.views++;
//eslint-disable-next-line
let addView =()=>( [
...state.filter(product => product.id !== action.id),
Object.assign({}, prod)
])
return state;
我不得不将状态修改部分放在一个名为 addView() 的函数中,否则我会看到该函数被无限地重复调用。如果有人可以帮我解决这个问题,我将不胜感激