React useEffect Hook不被调用,Redux操作不被触发

时间:2020-01-30 17:10:30

标签: javascript reactjs redux

每当我进入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

2 个答案:

答案 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))