簡體   English   中英

React useEffect Hook 不會被調用,Redux 動作不會被觸發

[英]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 creatorgetProduct並在鈎子中調用它——並且您沒有看到它在商店中發送,也沒有看到工作它在發火。 基於這些問題,這是因為您沒有調度 action

您需要使用react-reduxconnect將組件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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM