![](/img/trans.png)
[英]clearInterval does not get called inside React.useEffect hook
[英]React useEffect Hook does not get called, Redux action does not get fired
每當我轉到我的 EditProduct 頁面時,我都會調用 useEffect 來獲取 ID 為 match.params.id 的產品。 問題是鈎子沒有被調用。 老實說,我不知道為什么會這樣,或者根本不知道為什么會這樣,因為我在稱呼它應該如何稱呼它。 我還收到一個類型錯誤,說它無法讀取未定義的道具名稱,顯然如果產品為空,則無法讀取它的名稱。 我也在我的 redux devtools 中看到沒有任何動作被觸發,盡管 loadUser 是一個在站點重新渲染/掛載時應該總是被觸發的動作,我在 app.js 中設置了它。
我將在下面粘貼我的一些代碼和一個 github 存儲庫。
https://github.com/tigerabrodi/eBuy
編輯產品組件
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));
應用程序.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 });
};
認證減速器
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
我很確定useEffect
正在觸發,你應該在那里放一個日志,我保證你會看到它。
在我看來,使用您發布的代碼,您正在接收action creator 、 getProduct
並在鈎子中調用它——並且您沒有看到它在商店中發送,也沒有看到工作它在發火。 基於這些問題,這是因為您沒有調度 action 。
您需要使用react-redux
的connect
將組件connect
到 get dispatch
,或預先綁定要分派的動作創建者,或使用useDispatch
獲取對 dispatch 的引用,然后您需要包裝該dispatch(getProduct(...))
。
不相關,如果getDispatch
是一個靜態函數(意味着定義永遠不會改變),您不需要也不應該將它作為依賴項傳遞給useEffect
。 鈎子的依賴數組不是你在鈎子中引用的所有東西,而是所有的值,當它們改變時,應該導致鈎子重新運行。 換句話說,如果您只是將其歸結為產品 ID,那么無論何時使用新的產品 ID 呈現組件,效果鈎子都會重新運行該功能。 由於getProduct
是導入的並且永遠不會重新分配或更改,因此useEffect
永遠不會重新觸發該值,因此您可以將其從依賴項數組中刪除。
為了遵循這一點,我確實建議將dispatch
放在依賴項數組中,因為react-redux
在他們的文檔中沒有指出對dispatch
的引用是靜態的,因此您不應該假設它不會改變。
我認為問題在於withRouter
HOC 被connect
HOC“阻止”,並且沒有將路由器的數據傳遞給您的組件。
你應該改變肝卵圓細胞的順序( withRouter
在前):
withRouter(connect(mapStateToProps)(EditProduct))
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.