每当我进入EditProduct页面时,我都会调用useEffect来获取ID为match.params.id的产品。问题是挂钩没有被调用。老实说,我不知道为什么,或者为什么会这样,因为我称它应该如何称呼。我也收到类型错误,说它无法读取未定义的属性名称,如果产品为null,则无法读取其名称。 我还在redux devtools中也看到没有触发任何动作,尽管loadUser是在站点重新渲染/挂载时应该总是触发的动作,这是我在app.js中设置的。
我将在下面粘贴一些代码和github存储库。
https://github.com/tigerabrodi/eBuy
editProduct组件
import React, {Fragment, useState, useEffect} from 'react';
import {withRouter} from "react-router-dom";
import {connect} from "react-redux";
import {editProduct, getProduct} from '../../redux/product/product.actions';
import Spinner from '../layout/Spinner';
const EditProduct = ({history, editProduct, match, product: {loading, product}}) => {
useEffect(() => {
getProduct(match.params.id);
}, [getProduct, match.params.id]);
const [formData,
setFormData] = useState({name: product.name, description: product.description, price: product.price, image: ""});
const [showImage, setShowImage] = useState(false);
const [imageName, setImageName] = useState("");
const onChangeImage = e => {
setFormData({...formData, image: e.target.files[0]});
setShowImage(true);
setImageName(e.target.files[0].name);
}
const onChange = e => setFormData({
...formData,
[e.target.name]: e.target.value
});
const onSubmit = e => {
e.preventDefault();
editProduct(formData, history, match.params.id);
}
const {name, description, price} = formData;
return (
<Fragment>
<div className="container">
<div className="row">
{loading && (
<Spinner />
)}
<div className="col text-info font-weight-bold m-2">
*- All Fields Requried!
<form onSubmit={e => onSubmit(e)}>
<div className="form-group m-2">
<label htmlFor="name">Name</label>
<input type="text" placeholder="Enter Products Name" name="name" value={name} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div className="form-group m-2">
<label htmlFor="price">Price</label>
<input type="number" name="price" placeholder="Enter Products Price" value={price} onChange={e => onChange(e)} className="form-control" required/>
</div>
<div class="custom-file m-2">
<input type="file" onChange={e => onChangeImage(e)} class="custom-file-input bg-info" required/>
<label class="custom-file-label">{showImage ? imageName : "Upload Image"}</label>
</div>
<div className="form-group m-2">
<label htmlFor="title">Description</label>
<textarea name="description" onChange={e => onChange(e)} placeholder="Enter Products description" value={description} className="form-control" required/>
</div>
<input type="submit" value="Add Product" className="btn btn-block btn-info"/>
</form>
</div>
</div>
</div>
</Fragment>
);
}
const mapStateToProps = state => ({
product: state.product,
auth: state.auth
});
export default connect(mapStateToProps, {editProduct})(withRouter(EditProduct));
app.js
import './App.css';
import React, {Fragment, useEffect} from 'react';
import {Provider} from "react-redux";
import {BrowserRouter as Router, Route, Switch} from "react-router-dom";
import store from "./redux/store";
import setAuthToken from './utils/setAuthToken';
import { loadUser } from './redux/auth/auth.actions';
import Navbar from './components/layout/Navbar';
import Landing from './components/layout/Landing';
import Alert from './components/layout/Alert';
import Register from './components/auth/Register';
import Login from './components/auth/Login';
import PrivateRoute from './components/routing/PrivateRoute';
import Dashboard from './components/dashboard/Dashboard';
import CreateProduct from './components/product-forms/CreateProduct';
import Products from './components/products/Products';
import EditProduct from './components/product-forms/EditProduct';
if (localStorage.token) {
setAuthToken(localStorage.token)
}
const App = () => {
useEffect(() => {
store.dispatch(loadUser());
}, []);
return (
<Provider store={store}>
<Router>
<Fragment>
<Navbar />
<Alert />
<Route exact path="/" component={Landing} />
<Switch>
<Route exact path="/register" component={Register} />
<Route exact path="/login" component={Login} />
<PrivateRoute exact path="/dashboard" component={Dashboard} />
<PrivateRoute exact path="/add-product" component={CreateProduct} />
<PrivateRoute exact path="/products" component={Products} />
<PrivateRoute exact path="/products/edit/:id" component={EditProduct} />
</Switch>
</Fragment>
</Router>
</Provider>
);
}
export default App;
产品动作
import {ProductActionTypes} from "./product.types"
import {setAlert} from "../alert/alert.actions"
import axios from "axios"
// Add A Product
export const addProduct = (productData, history) => async dispatch => {
const formData = new FormData();
formData.append("name", productData.name);
formData.append("description", productData.description);
formData.append("price", productData.price);
formData.append("image", productData.image);
try {
const res = await axios.post("/products", formData);
dispatch({
type: ProductActionTypes.ADD_PRODUCT,
payload: res.data
});
history.push("/dashboard");
dispatch(setAlert("Product created successfully", "success"))
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Get all products
export const getAllProducts = page => async dispatch => {
try {
const res = await axios.get(`/products?page=${page}`);
dispatch({
type: ProductActionTypes.GET_PRODUCTS,
payload: {products: res.data.products, totalItems: res.data.totalItems}
})
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: err
})
}
}
// Delete a product
export const deleteSingleProduct = id => async dispatch => {
try {
await axios.delete(`/products/${id}`);
dispatch({
type: ProductActionTypes.DELETE_PRODUCT,
payload: id
});
dispatch(setAlert("Product deleted successfully", "success"))
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Get A Single users products
export const getUserProducts = (id, page) => async dispatch => {
try {
const res = await axios.get(`/products/${id}?page=${page}`);
dispatch({
type: ProductActionTypes.GET_PRODUCTS,
payload: res.data
})
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Edit a Product
export const editProduct = (productData, history, id) => async dispatch => {
const formData = new FormData();
formData.append("name", productData.name);
formData.append("description", productData.description);
formData.append("price", productData.price);
formData.append("image", productData.image);
try {
const res = await axios.put(`/products/edit/${id}`, formData);
dispatch({
type: ProductActionTypes.UPDATE_PRODUCT,
payload: res.data
});
dispatch(setAlert("Product updated successfully", "success"))
history.push("/dashboard")
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
})
}
}
// Get a single product by ID
export const getProduct = id => async dispatch => {
try {
const res = await axios.get(`/products/product/${id}`);
dispatch({
type: ProductActionTypes.GET_PRODUCT,
payload: res.data
});
} catch (err) {
dispatch({
type: ProductActionTypes.PRODUCT_ERROR,
payload: {msg: err.response.statusText, status: err.response.status}
});
}
}
产品减速器
import {ProductActionTypes} from "./product.types";
const initialState = {
products: [],
totalProducts: null,
product: null,
loading: true,
error: {}
}
const productReducer = (state = initialState, action) => {
const {payload, type} = action;
switch(type) {
case ProductActionTypes.GET_PRODUCTS:
return {
...state,
products: payload.products,
totalProducts: payload.totalItems,
loading: false
}
case ProductActionTypes.GET_PRODUCT:
return {
...state,
product: payload,
loading: false
}
case ProductActionTypes.ADD_PRODUCT:
return {
...state,
products: [payload, ...state.products],
loading: false
}
case ProductActionTypes.UPDATE_PRODUCT:
return {
...state,
products: state.products.map(product => product._id === payload.id ? {product: payload.product} : product),
loading: false
}
case ProductActionTypes.DELETE_PRODUCT:
return {
...state,
products: state.products.filter(product => product._id !== payload),
loading: false
}
case ProductActionTypes.PRODUCT_ERROR:
return {
...state,
error: payload,
loading: false
}
default:
return state;
}
}
export default productReducer
身份验证动作
import axios from "axios";
import {setAlert} from "../alert/alert.actions"
import {AuthActionTypes} from "./auth.types"
import setAuthToken from "../../utils/setAuthToken"
// Load User
export const loadUser = () => async dispatch => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get('/auth');
dispatch({
type: AuthActionTypes.USER_LOADED,
payload: res.data
});
} catch (err) {
dispatch({
type: AuthActionTypes.AUTH_ERROR
});
}
};
// Register User
export const register = ({ name, email, password }) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post('/auth/signup', body, config);
dispatch({
type: AuthActionTypes.REGISTER_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: AuthActionTypes.REGISTER_FAIL
});
}
};
// Login User
export const login = (email, password) => async dispatch => {
const config = {
headers: {
'Content-Type': 'application/json'
}
};
const body = JSON.stringify({ email, password });
try {
const res = await axios.post('/auth/signin', body, config);
dispatch({
type: AuthActionTypes.LOGIN_SUCCESS,
payload: res.data
});
dispatch(loadUser());
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach(error => dispatch(setAlert(error.msg, 'danger')));
}
dispatch({
type: AuthActionTypes.LOGIN_FAIL
});
}
};
// Logout / Clear Profile
export const logout = () => dispatch => {
dispatch({ type: AuthActionTypes.LOGOUT });
};
auth reducer
import {AuthActionTypes} from "./auth.types";
const initialState = {
token: localStorage.getItem("token"),
isAuthenticated: null,
loading: true,
user: null
}
const authReducer = (state = initialState, action) => {
const {type, payload} = action;
switch (type) {
case AuthActionTypes.USER_LOADED:
return {
...state,
isAuthenticated: true,
loading: false,
user: payload
};
case AuthActionTypes.REGISTER_SUCCESS:
case AuthActionTypes.LOGIN_SUCCESS:
localStorage.setItem('token', payload.token);
return {
...state,
...payload,
isAuthenticated: true,
loading: false
};
case AuthActionTypes.REGISTER_FAIL:
case AuthActionTypes.AUTH_ERROR:
case AuthActionTypes.LOGIN_FAIL:
case AuthActionTypes.LOGOUT:
case AuthActionTypes.ACCOUNT_DELETED:
case AuthActionTypes.USER_ERROR:
localStorage.removeItem('token');
return {
...state,
token: null,
isAuthenticated: false,
loading: false
};
default:
return state;
}
}
export default authReducer
答案 0 :(得分:0)
我很确定useEffect
正在触发,您应该在其中放置一个日志,我保证您会看到它。
对我来说,您发布的代码是您要加入动作创建者 ,getProduct
,并在钩子中调用它-而您并没有看到它在商店中分派,而您却没有看到它正在做的工作。基于这些问题,这是因为您没有调度动作。
您需要使用react-redux
的{{1}}连接组件以获取connect
,或预先绑定要分派的动作创建者,或使用dispatch
以获得要分发的参考,然后您需要包装useDispatch
。
不相关,如果dispatch(getProduct(...))
是静态函数(意味着定义永远不变),则不需要,也不应将其作为依赖项传递给getDispatch
。钩子的依赖项数组并不适用于您在钩子中引用的所有内容,而是适用于当它们更改时应导致钩子重新运行的所有值。换句话说,如果仅将其简化为产品ID,则只要使用新的产品ID渲染组件,效果钩子就会重新运行该功能。并且由于useEffect
是导入的,并且永远不会重新分配或更改,因此getProduct
将永远不会重新触发该值,因此您可以将其从依赖项数组中删除。
接下来,我建议您将useEffect
放入依赖项数组中,因为dispatch
在其文档中没有指出对react-redux
的引用是静态的,因此您不应假定它将不变。
答案 1 :(得分:0)
我认为问题在于withRouter
HOC被connect
HOC阻止了,并且没有将路由器的数据传递给您的组件。
您应该更改HOCS的顺序(首先{withRouter
):
withRouter(connect(mapStateToProps)(EditProduct))